Skip to content

Commit 6f495fe

Browse files
committed
feat: {{Annotate:}} DocGen tag with Pretext scanline reflow engine
- New {{Annotate:}} DocGen tag: canvas annotation overlay for any @text:, @img:, or @url: source - @text: mode renders text on-canvas with real-time Pretext-style scanline reflow (~0.5-1.5ms/frame) - Scanline engine: offscreen mask canvas + per-row getImageData + word packing via measureText arithmetic - Tools: pen, highlighter, eraser, line, arrow, rect, circle; color swatches + size slider - Present button calls M.setViewMode('preview') — hides editor, annotation stays drawable while reading - Undo/Clear/PNG export actions; Clear instant (no confirm), undo-stack preserved - Fixed: data-text stripped by DOMPurify — added data-text, data-reflow to ADD_ATTR whitelist - Fixed: text stored as hidden ann-reflow-text span textContent (survives sanitization without whitelist) - annotate-docgen.js (~710 lines) + annotate-docgen.css (~340 lines) - pretext-resize.js: dynamic canvas resize utility - Pretext reflow interactive demo: 4 tabs (Float Image, Draw Exclusion, Both Together, API explainer)
1 parent b8f783c commit 6f495fe

21 files changed

Lines changed: 1619 additions & 18 deletions
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Annotate DocGen — Pretext Scanline Reflow Engine
2+
3+
- New `{{Annotate:}}` DocGen tag with canvas-based freehand annotation overlay
4+
- New `@text:` source mode renders text on-canvas with Pretext-style scanline reflow
5+
- Scanline engine (`reflowText`, `getFreeIntervals`) samples 1px pixel rows from an offscreen mask canvas to compute per-line free intervals
6+
- Real-time text reflow: as user draws, each stroke updates the mask and text re-routes live around strokes
7+
- `layoutNextLine()` pattern used per-row: per-line width narrows when a stroke occupies that row's x-range
8+
- Offscreen mask canvas approach: strokes drawn at full resolution, scanline reads `getImageData(0, y, width, 1)` — O(width) per row, ~0.5–1.5ms total
9+
- Added `↩ Undo` button with stroke history stack (Ctrl+Z equivalent)
10+
- Added `🗑 Clear` button (no confirm dialog) with undo stack support
11+
- Added `📥 PNG` button — exports composite canvas (strokes + text) as PNG download
12+
- Added `📖 Present` button — calls `M.setViewMode('preview')` to hide editor and enter live reading/drawing mode
13+
- `Present` auto-scrolls annotation card into view after mode switch
14+
- Removed blocking `confirm()` dialog from Clear; instant clear with undo restore
15+
- Canvas auto-resizes to match `ann-source-text-reflow` element height or defaults to 320px
16+
- `redrawAll` handles dual-layer rendering: text layer first, strokes on top
17+
- Fixed: `data-text` attribute stripped by DOMPurify — added `data-text`, `data-reflow` to `ADD_ATTR` whitelist in `renderer.js`
18+
- Fixed: Text now stored as hidden `<span class="ann-reflow-text">` textContent inside the reflow div — textContent always survives DOMPurify regardless of attribute whitelist
19+
- Fixed: `initCanvas` reads from `textSpan.textContent` first (bulletproof), falls back to `dataset.text`
20+
- Fixed: `M.insertAtCursor` replaced with `M.setViewMode('preview')` for the insert action — annotation stays live and drawable instead of becoming a static image
21+
- Fixed: CORS/SecurityError on external images handled with try/catch and fallback to annotation-only export
22+
- Fixed: Image insertion uses `gen-img:ID` registry pattern (same as `draw-docgen.js`) to bypass DOMPurify's stripping of `data:` URLs
23+
- Added `.ann-present-btn` CSS with green gradient, `font-weight: 600`, and hover transform
24+
- Added `.ann-reflow-text` CSS: visually hidden span (position absolute, 1×1px clip) so stored text is invisible but accessible to canvas reader
25+
- New `public/pretext-reflow-demo.html` — 4-tab interactive demo: Float Image, Draw Exclusion, Both Together, How It Works
26+
- Float Image tab: image float left/right with width %, top offset sliders, image picker (picsum.photos), and own-image upload
27+
- Draw Exclusion tab: side-by-side DOM (text buried) vs Pretext (scanline reflow) comparison
28+
- Both Together tab: image float + freehand strokes combined, both exclude text simultaneously
29+
- How It Works tab: annotated `layoutNextLine()` code explanation with image and scanline approach
30+
- New `css/annotate-docgen.css` with full card UI, toolbar, color swatches, size slider, dark mode
31+
- New `js/annotate-docgen.js` (~710 lines): parser, canvas init, scanline engine, stroke management, export, present
32+
33+
---
34+
35+
## Summary
36+
37+
Integrates a real-time, pixel-precise Pretext-style text reflow engine into the `{{Annotate:}}` DocGen tag. Text flows live around freehand annotations using a scanline mask canvas algorithm (O(width) per row, ~0.5–1.5ms/frame). The "Present" button transitions TextAgent to preview-only mode, keeping the annotation card fully drawable while reading. A companion interactive demo (`pretext-reflow-demo.html`) illustrates all three reflow scenarios: image float, freehand, and both combined.
38+
39+
---
40+
41+
## 1. Annotate DocGen Tag — New `{{Annotate:}}` tag
42+
43+
**Files:** `js/annotate-docgen.js`, `css/annotate-docgen.css`
44+
**What:** Full DocGen card with toolbar (pen/highlighter/eraser/line/arrow/rect/circle), color swatches, size slider, canvas overlay over any `@text:`, `@img:`, or `@url:` source. Undo/Clear/PNG/Present buttons in header.
45+
**Impact:** Authors can write `{{Annotate: Title @text: body text}}` in markdown and get a live, interactive annotation canvas with real-time Pretext text reflow.
46+
47+
---
48+
49+
## 2. Scanline Reflow Engine
50+
51+
**Files:** `js/annotate-docgen.js` (`reflowText`, `getFreeIntervals`, `buildMask`)
52+
**What:** Offscreen mask canvas accumulates stroke paths. Per text row, `getImageData(0, y, width, 1)` scans a single pixel row for occupied x-ranges. `getFreeIntervals` returns free segments. `reflowText` packs words into free intervals using `canvas.measureText()` for width arithmetic — exactly the Pretext `layoutNextLine()` pattern.
53+
**Impact:** Text wraps with pixel precision around any freehand shape drawn by the user. Performance: ~0.5–1.5ms per full reflow at typical canvas sizes.
54+
55+
---
56+
57+
## 3. DOMPurify Fix — `data-text` Whitelist + Hidden Span
58+
59+
**Files:** `js/renderer.js`, `js/annotate-docgen.js`, `css/annotate-docgen.css`
60+
**What:** `data-text` and `data-reflow` added to `ADD_ATTR` in `renderer.js`. Additionally, text now stored inside a hidden `<span class="ann-reflow-text">` (position absolute, 1×1px clip) as textContent — survives DOMPurify with zero whitelisting needed. `initCanvas` reads textContent first, `dataset.text` as fallback.
61+
**Impact:** Fixed core bug where `@text:` content was silently dropped by DOMPurify, leaving a blank canvas with no text rendered.
62+
63+
---
64+
65+
## 4. Present Mode — Live Reading + Drawing
66+
67+
**Files:** `js/annotate-docgen.js`, `css/annotate-docgen.css`
68+
**What:** "📖 Present" button replaces "Insert". Calls `M.setViewMode('preview')` to hide the editor and expand preview to full width. Annotation card remains a live canvas — user reads and draws simultaneously. Falls back to manual CSS hide of `.editor-pane` if `M.setViewMode` is unavailable.
69+
**Impact:** Completes the author workflow: write → annotate → present. The annotation is never "frozen" as a static image — it stays interactive.
70+
71+
---
72+
73+
## 5. Pretext Reflow Demo
74+
75+
**Files:** `public/pretext-reflow-demo.html`
76+
**What:** Self-contained 4-tab demo showing: (1) float image with `layoutNextLine()` per-line width narrowing, (2) freehand scanline reflow vs DOM comparison, (3) image + strokes combined, (4) API explainer. Image picker uses picsum.photos; own-image upload supported. Performance metrics shown live.
77+
**Impact:** Demonstrates all three `layoutNextLine()` reflow patterns in an interactive standalone page. Accessible at `/pretext-reflow-demo.html`.
78+
79+
---
80+
81+
## Files Changed (6 total)
82+
83+
| File | Lines Changed | Type |
84+
|------|:---:|------|
85+
| `js/annotate-docgen.js` | +710 | New module — full Annotate DocGen |
86+
| `css/annotate-docgen.css` | +340 | New stylesheet — card, toolbar, reflow badge |
87+
| `js/renderer.js` | +2 | DOMPurify ADD_ATTR: added `data-text`, `data-reflow` |
88+
| `src/main.js` | +5 | Phase lazy-load registration for annotate-docgen |
89+
| `public/pretext-reflow-demo.html` | +720 | New interactive Pretext reflow demo |
90+
| `public/annotate-example.md` | +12 | Example markdown using `{{Annotate:}}` tag |

CHANGELOG-pretext-performance.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Pretext Performance Optimization — Eliminate Layout Thrashing in Textarea Resize
2+
3+
- Added `js/pretext-resize.js`: shared canvas-based textarea resize utility (zero DOM reflows)
4+
- Integrated `@chenglou/pretext` npm library for GPU font metrics measurement
5+
- Wired `pretextResize` into `window.MDView.pretextResize` for IIFE module access
6+
- Imported `pretext-resize.js` in `src/main.js` Phase 1 (Core) load order
7+
- Patched `js/ai-chat.js`: replaced double-reflow auto-resize with `M.pretextResize(aiInput, 120)`
8+
- Patched `js/composer.js`: replaced double-reflow `autoResize()` with `M.pretextResize(inputEl, 120)`
9+
- Patched `js/ai-tags.js`: replaced double-reflow thread panel textarea resize with `M.pretextResize(this, 80)`
10+
- Patched `js/ai-docgen.js`: replaced both input-event and initial-height double-reflows in prompt textarea
11+
- Patched `js/quiz-docgen.js`: replaced both double-reflows in prompt textarea (input + initial height)
12+
- Patched `js/game-docgen.js`: replaced both double-reflows in game prompt textarea
13+
- Patched `js/tools-docgen.js`: replaced both double-reflows in tools input textarea
14+
- Patched `js/git-docgen.js`: replaced both double-reflows in git prompt textarea
15+
- Patched `js/draw-docgen.js`: replaced three double-reflow sites (AI gen + mermaid input + initial height)
16+
- Added graceful fallback: all patches keep the old `scrollHeight` path when `M.pretextResize` is not yet loaded
17+
- Added `public/pretext-demo.html`: interactive benchmark comparing 0-reflow vs 91-reflow behavior
18+
19+
---
20+
21+
## Summary
22+
Replaced the `el.style.height = 'auto'; el.style.height = el.scrollHeight + 'px'` double-reflow pattern across all 9 textarea hotspots with a single canvas-based write using the Pretext library. This eliminates synchronous page-wide reflows on every keystroke, returning 4–8ms per keypress to the main thread and significantly improving responsiveness during high-load sessions (e.g., AI chat with active markdown preview).
23+
24+
---
25+
26+
## 1. Pretext Utility Module (New)
27+
**Files:** `js/pretext-resize.js`, `src/main.js`, `package.json`, `package-lock.json`
28+
**What:** Created a shared `pretextResize(el, maxH)` function that uses `canvas.measureText()` to calculate text geometry without touching DOM layout. Guards: visibility check (clientWidth), font aliasing (`system-ui``Helvetica Neue`), dynamic padding/line-height from `getComputedStyle`. Exposed as `window.MDView.pretextResize`. Imported in Phase 1 of `main.js`.
29+
**Impact:** All UI modules can access the utility before any IIFE initializes. Zero side-effects on existing behavior—old path runs as fallback.
30+
31+
---
32+
33+
## 2. AI Chat Input
34+
**Files:** `js/ai-chat.js`
35+
**What:** Replaced the 4-line double-reflow block in the `aiInput` event listener with a 1-line `M.pretextResize(aiInput, 120)` call guarded by a feature-check.
36+
**Impact:** The primary user-facing typing surface now causes 0 reflows per keystroke (down from 2).
37+
38+
---
39+
40+
## 3. Composer Input
41+
**Files:** `js/composer.js`
42+
**What:** Replaced the internal `autoResize()` function body with `M.pretextResize(inputEl, 120)`.
43+
**Impact:** Composer sends 0 reflows on resize instead of 2.
44+
45+
---
46+
47+
## 4. AI Tags Thread Textarea
48+
**Files:** `js/ai-tags.js`
49+
**What:** Replaced the thread panel textarea's double-reflow with `M.pretextResize(this, 80)`.
50+
**Impact:** Zero reflows per keystroke in sidebar thread inputs.
51+
52+
---
53+
54+
## 5. DocGen Prompt Textareas (6 files)
55+
**Files:** `js/ai-docgen.js`, `js/quiz-docgen.js`, `js/game-docgen.js`, `js/tools-docgen.js`, `js/git-docgen.js`, `js/draw-docgen.js`
56+
**What:** Each file had 2–3 double-reflow sites (one in the `input` event handler, one for initial height on load, and in draw-docgen an additional site after AI code gen). All replaced with `M.pretextResize()`.
57+
**Impact:** Tag card prompt textareas now resize without any layout work. Initial height is set via canvas measurement on first render.
58+
59+
---
60+
61+
## Files Changed (12 total)
62+
63+
| File | Lines Changed | Type |
64+
|------|:---:|------|
65+
| `js/pretext-resize.js` | +60 | New utility module |
66+
| `src/main.js` | +3 | Import pretext-resize in Phase 1 |
67+
| `js/ai-chat.js` | +1 | Feature-guarded resize replacement |
68+
| `js/composer.js` | +1 | Feature-guarded resize replacement |
69+
| `js/ai-tags.js` | +1 | Feature-guarded resize replacement |
70+
| `js/ai-docgen.js` | +2 −2 | Input + initial height replaced |
71+
| `js/quiz-docgen.js` | +2 −2 | Input + initial height replaced |
72+
| `js/game-docgen.js` | +2 −2 | Input + initial height replaced |
73+
| `js/tools-docgen.js` | +2 −2 | Input + initial height replaced |
74+
| `js/git-docgen.js` | +2 −2 | Input + initial height replaced |
75+
| `js/draw-docgen.js` | +3 −3 | 3 sites replaced (AI gen + input + initial) |
76+
| `public/pretext-demo.html` | +210 | Interactive benchmark demo page |

0 commit comments

Comments
 (0)