# Editor Architecture Plan (Canvas, Linked Pages)

## Goals
- Real pages in the DOM (fixed size, visible as separate sheets).
- One unified document model (plain text now, formatting later).
- Fast rendering with 400+ pages.
- Deterministic layout (Times New Roman 11px, line-height 18px).
- Native scroll (no fake scrollbar).

## Key Decision
Use **page shells** for scrolling and **4 reusable canvases** for rendering text, similar to Google Docs.

## Fixed Typography (MVP)
- Font: Times New Roman
- Font size: 11px
- Line height: 18px
- Blocks: paragraphs only (split on `\n`)
- Storage: plain text

## Rendering Strategy (Approved)
### Page Shells
- Create one `div.page-shell` per page.
- These shells provide size, margins, shadow, and create the native scroll.
- Total DOM pages = total page count (can be 400+).

### Render Slots (Canvas)
- Keep **only 4 canvas elements** in DOM at any time (MVP).
- On scroll, move canvas slots into the visible page shells.
- Render only the text for those pages.

### Why
- Native scroll stays intact.
- Minimal heavy rendering (4 canvases total).
- Scales to hundreds of pages.

## Core Modules

### 1) DocumentModel
- Source of truth: `text: string`.
- Cursor position = global offset.
- Paragraphs = split by `\n`.
- Later: inline styles and block types (H1/H2).

### 2) LayoutEngine
Input:
- text
- typography (font, size, lineHeight)
- page size + padding

Output:
- `lines[]`: [{ start, end, text, pageIndex, x, y }]
- `pages[]`: [{ index, lineStart, lineEnd }]
- Mapping functions:
  - offset -> (pageIndex, lineIndex, x)
  - (pageIndex, x, y) -> offset

### 3) Renderer
- Maintains render slots (canvas).
- Maintains all page shells.
- On scroll:
  - compute visible page range
  - assign canvases to visible pages
  - render lines for those pages

### 4) InputController
- Hidden textarea captures input.
- Key handling:
  - arrows, enter, backspace
  - selection in future
- Converts input to model edits.
- Triggers re-layout and render.

### 5) Caret / Selection
- Caret drawn as overlay (div or canvas).
- Selection drawn as rectangles per line.
- All positions driven by layout mapping.

## Page Shell + Canvas Wiring
- `page-shell[data-page-index]`
- `canvas-slot[data-slot-index]`
- On scroll:
  - visible range = currentPage ... currentPage+3
  - move canvas-slot into those shells
  - draw page text

## Layout Details (MVP)
- Use canvas.measureText for line breaking.
- Hard wrap at word boundaries.
- Paragraph break = new line.
- Page break every `linesPerPage = floor(contentHeight / lineHeight)`.

## Performance Notes
- Layout is deterministic and cached.
- Only redraw on edits or resize.
- Render only 4 pages at a time.

## Roadmap
### Phase 1 (MVP)
- Plain text editing
- Enter/backspace
- Arrow navigation
- Pages with canvas rendering

### Phase 2
- Selection (mouse + shift)
- Copy/paste

### Phase 3
- Block types (H1/H2)
- Inline styles (bold/italic)
- Comments + highlights

## Open Questions
- Selection behavior across pages
- IME support (future)
- Undo/redo stack

---

## Optimizations & Improvements (Adopted)

### 1) Incremental Layout (Paragraph Cache)
Cache layout per paragraph and invalidate only the modified paragraph.

Rules:
- If lineCount changes in a paragraph, recalculate all following paragraphs.
- If lineCount stays the same, only shift `y` for subsequent lines.

```ts
type ParagraphCache = Map<number, {
  text: string;
  lines: Line[];
  hash: number;  // for invalidation
}>;
```

### 2) OffscreenCanvas for Measurements
Use OffscreenCanvas for text measurement (fallback to normal canvas).

```ts
const measureCanvas = new OffscreenCanvas(1, 1);
const measureCtx = measureCanvas.getContext('2d');
measureCtx.font = '11px Times New Roman';
```

### 3) Buffer Slots for Fast Scrolling
Use extra render slots beyond the visible pages.

```
visibleSlots = 4
bufferSlots = 2
totalSlots = 6-8
```

### 4) Layered Rendering for Selection
Selection and caret update frequently, so separate layers:

```
Layer 1: Canvas with text (rarely changes)
Layer 2: Canvas/div with selection overlay (changes on drag)
Layer 3: Div with caret (blinks)
```

### 5) Extended Line Structure
```ts
type Line = {
  start: number;
  end: number;
  text: string;
  pageIndex: number;
  x: number;
  y: number;
  width: number;           // for hit-testing
  paragraphIndex: number;  // for incremental relayout
  charOffsets: number[];   // O(1) cursor positioning for visible pages
};
```

- `charOffsets` should be computed only for visible pages to save memory.

### 6) Background Layout with requestIdleCallback
Render visible pages immediately, calculate the rest in idle time:

```ts
layoutVisiblePages();
requestIdleCallback(() => layoutRemainingPages());
```

### 7) Binary Search Utilities
With ~20,000 lines, avoid linear scans:

```ts
function findLineByOffset(offset: number): Line {
  let lo = 0, hi = lines.length - 1;
  while (lo < hi) {
    const mid = (lo + hi + 1) >> 1;
    if (lines[mid].start <= offset) lo = mid;
    else hi = mid - 1;
  }
  return lines[lo];
}
```

---

## Formatting Model (Future)

### Range-Based Formatting
Keep document text as plain string, store formatting as separate ranges.
This keeps offsets simple and makes layout/rendering predictable.

Example:
```
{
  "text": "Hello world",
  "formats": [
    { "type": "bold", "start": 0, "end": 5 },
    { "type": "italic", "start": 6, "end": 11 }
  ]
}
```

### Notes
- Ranges can overlap (bold + italic).
- On insert/delete, shift ranges after the edit point.
- If an edit splits a range, split the range accordingly.
- Rendering: compute active styles per segment in layout.

### Planned Types
```
type FormatRange = { type: 'bold' | 'italic' | 'underline'; start: number; end: number }
type Doc = { text: string; formats: FormatRange[] }
```
