@pre-markdown/layout 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +1365 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +660 -0
- package/dist/index.d.ts +660 -0
- package/dist/index.js +1348 -0
- package/dist/index.js.map +1 -0
- package/dist/worker-script.js +1867 -0
- package/dist/worker-script.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/worker-backend.ts","../src/virtual-list.ts","../src/cursor.ts","../src/line-renderer.ts"],"sourcesContent":["/**\n * @pre-markdown/layout\n *\n * Pretext-based text layout engine.\n * Handles text measurement, line breaking, and virtual viewport layout.\n *\n * Uses @chenglou/pretext for zero-DOM-reflow text measurement:\n * prepare() → one-time text analysis (cached)\n * layout() → pure-arithmetic line breaking (hot path)\n */\n\nimport {\n prepare,\n prepareWithSegments,\n layout,\n layoutWithLines,\n clearCache as pretextClearCache,\n setLocale as pretextSetLocale,\n} from '@chenglou/pretext'\n\nimport type {\n PreparedText,\n PreparedTextWithSegments,\n LayoutResult as PretextLayoutResult,\n LayoutLinesResult as PretextLinesResult,\n LayoutLine as PretextLine,\n PrepareOptions,\n} from '@chenglou/pretext'\n\n// Re-export pretext types for consumers\nexport type { PreparedText, PreparedTextWithSegments, PretextLine }\n\n// ============================================================\n// Configuration Types\n// ============================================================\n\nexport interface LayoutConfig {\n /** CSS font string (e.g., '16px Inter'). Must be loaded before use. */\n font: string\n /** Line height in pixels (must match CSS line-height) */\n lineHeight: number\n /** Maximum width for text wrapping (pixels) */\n maxWidth: number\n /** White-space mode: 'normal' (default) or 'pre-wrap' */\n whiteSpace?: 'normal' | 'pre-wrap'\n /** Viewport buffer multiplier (default 2 = 2x viewport above & below) */\n viewportBuffer?: number\n /** Font for code blocks / inline code (defaults to main font) */\n codeFont?: string\n /** Line height for code blocks (defaults to main lineHeight) */\n codeLineHeight?: number\n}\n\n// ============================================================\n// Result Types\n// ============================================================\n\nexport interface LayoutLine {\n /** Line text content */\n text: string\n /** Measured width in pixels */\n width: number\n /** Y position from top */\n y: number\n /** Source line index (in document paragraphs) */\n sourceIndex: number\n}\n\nexport interface LayoutResult {\n /** Total height of all lines */\n height: number\n /** Total number of visual lines */\n lineCount: number\n /** Individual line layouts (only populated when requested) */\n lines?: LayoutLine[]\n}\n\nexport interface ViewportLayoutResult {\n /** Lines visible in the viewport */\n visibleLines: LayoutLine[]\n /** Total height of the entire document */\n totalHeight: number\n /** Y offset of the first visible line */\n startY: number\n /** Index of first visible line */\n startIndex: number\n /** Index of last visible line (exclusive) */\n endIndex: number\n}\n\n// ============================================================\n// LRU Cache\n// ============================================================\n\ninterface CacheEntry<T> {\n value: T\n}\n\nclass LRUCache<T> {\n private map = new Map<string, CacheEntry<T>>()\n private maxSize: number\n\n constructor(maxSize = 512) {\n this.maxSize = maxSize\n }\n\n get(key: string): T | undefined {\n const entry = this.map.get(key)\n if (!entry) return undefined\n // Move to end (most recently used) — Map preserves insertion order\n this.map.delete(key)\n this.map.set(key, entry)\n return entry.value\n }\n\n set(key: string, value: T): void {\n if (this.map.has(key)) {\n this.map.delete(key)\n } else if (this.map.size >= this.maxSize) {\n // Evict oldest (first entry in Map — O(1))\n const first = this.map.keys().next().value\n if (first !== undefined) this.map.delete(first)\n }\n this.map.set(key, { value })\n }\n\n has(key: string): boolean {\n return this.map.has(key)\n }\n\n delete(key: string): boolean {\n return this.map.delete(key)\n }\n\n clear(): void {\n this.map.clear()\n }\n\n get size(): number {\n return this.map.size\n }\n}\n\n// ============================================================\n// Measurement Backend (pluggable for Node.js testing)\n// ============================================================\n\nexport interface MeasurementBackend {\n /** One-time text analysis (expensive, cache result) */\n prepare(text: string, font: string, options?: PrepareOptions): PreparedText\n /** One-time text analysis with segment info */\n prepareWithSegments(text: string, font: string, options?: PrepareOptions): PreparedTextWithSegments\n /** Pure-arithmetic layout (cheap, can call per frame) */\n layout(prepared: PreparedText, maxWidth: number, lineHeight: number): PretextLayoutResult\n /** Layout with per-line info */\n layoutWithLines(prepared: PreparedTextWithSegments, maxWidth: number, lineHeight: number): PretextLinesResult\n /** Clear internal caches */\n clearCache(): void\n /** Set locale for text segmentation */\n setLocale(locale?: string): void\n}\n\n/** Default backend: real @chenglou/pretext (requires Canvas) */\nconst pretextBackend: MeasurementBackend = {\n prepare,\n prepareWithSegments,\n layout,\n layoutWithLines,\n clearCache: pretextClearCache,\n setLocale: pretextSetLocale,\n}\n\n/**\n * Fallback backend for environments without Canvas (e.g. Node.js tests).\n * Uses character-count heuristic for approximate measurement.\n */\nexport function createFallbackBackend(avgCharWidth = 8): MeasurementBackend {\n return {\n prepare(text: string, _font: string, _options?: PrepareOptions): PreparedText {\n // Return a fake PreparedText that stores the text\n return { __fallback: true, text } as unknown as PreparedText\n },\n prepareWithSegments(text: string, _font: string, _options?: PrepareOptions): PreparedTextWithSegments {\n return { __fallback: true, text, segments: text.split(/(\\s+)/).filter(Boolean) } as unknown as PreparedTextWithSegments\n },\n layout(prepared: PreparedText, maxWidth: number, lineHeight: number): PretextLayoutResult {\n const text = (prepared as unknown as { text: string }).text ?? ''\n const lines = estimateLines(text, maxWidth, avgCharWidth)\n return { height: lines * lineHeight, lineCount: lines }\n },\n layoutWithLines(prepared: PreparedTextWithSegments, maxWidth: number, lineHeight: number): PretextLinesResult {\n const text = (prepared as unknown as { text: string }).text ?? ''\n const wrappedLines = wrapText(text, maxWidth, avgCharWidth)\n const lines = wrappedLines.map((line, i) => ({\n text: line,\n width: line.length * avgCharWidth,\n start: { segmentIndex: 0, graphemeIndex: 0 },\n end: { segmentIndex: 0, graphemeIndex: 0 },\n }))\n return {\n height: lines.length * lineHeight,\n lineCount: lines.length,\n lines,\n }\n },\n clearCache() { /* no-op */ },\n setLocale() { /* no-op */ },\n }\n}\n\nfunction estimateLines(text: string, maxWidth: number, avgCharWidth: number): number {\n if (text.length === 0) return 1\n const hardLines = text.split('\\n')\n let total = 0\n const charsPerLine = Math.max(1, Math.floor(maxWidth / avgCharWidth))\n for (const line of hardLines) {\n total += Math.max(1, Math.ceil(line.length / charsPerLine))\n }\n return total\n}\n\nfunction wrapText(text: string, maxWidth: number, avgCharWidth: number): string[] {\n const charsPerLine = Math.max(1, Math.floor(maxWidth / avgCharWidth))\n const result: string[] = []\n const hardLines = text.split('\\n')\n for (const line of hardLines) {\n if (line.length <= charsPerLine) {\n result.push(line)\n } else {\n for (let i = 0; i < line.length; i += charsPerLine) {\n result.push(line.slice(i, i + charsPerLine))\n }\n }\n }\n return result.length > 0 ? result : ['']\n}\n\n// ============================================================\n// Layout Engine\n// ============================================================\n\n/**\n * High-performance text layout engine powered by @chenglou/pretext.\n *\n * Two-phase pipeline:\n * 1. prepare() — one-time text analysis (~1-5ms per paragraph, cached via LRU)\n * 2. layout() — pure arithmetic (~0.0002ms, safe for animation frames)\n *\n * In environments without Canvas (Node.js), set a fallback backend\n * via the constructor or `setBackend()`.\n */\nexport class LayoutEngine {\n private config: LayoutConfig\n private backend: MeasurementBackend\n private preparedCache: LRUCache<PreparedText>\n private preparedSegCache: LRUCache<PreparedTextWithSegments>\n\n constructor(config: LayoutConfig, backend?: MeasurementBackend) {\n this.config = {\n viewportBuffer: 2,\n whiteSpace: 'normal',\n ...config,\n }\n this.backend = backend ?? pretextBackend\n this.preparedCache = new LRUCache(512)\n this.preparedSegCache = new LRUCache(256)\n }\n\n // --------------------------------------------------------\n // Configuration\n // --------------------------------------------------------\n\n /** Update layout configuration. Invalidates cache if font changes. */\n updateConfig(config: Partial<LayoutConfig>): void {\n const fontChanged = config.font && config.font !== this.config.font\n this.config = { ...this.config, ...config }\n if (fontChanged) {\n this.clearAllCaches()\n }\n }\n\n /** Get current configuration (readonly). */\n getConfig(): Readonly<LayoutConfig> {\n return this.config\n }\n\n /** Replace the measurement backend (e.g. for testing). */\n setBackend(backend: MeasurementBackend): void {\n this.backend = backend\n this.clearAllCaches()\n }\n\n /** Set locale for text segmentation. */\n setLocale(locale?: string): void {\n this.backend.setLocale(locale)\n this.clearAllCaches()\n }\n\n // --------------------------------------------------------\n // Core Layout API\n // --------------------------------------------------------\n\n /**\n * Compute height and line count for a text block.\n * Uses pretext prepare() + layout() pipeline.\n * The PreparedText is cached by (text, font) key.\n */\n computeLayout(text: string): LayoutResult {\n const prepared = this.getPrepared(text)\n const result = this.backend.layout(prepared, this.config.maxWidth, this.config.lineHeight)\n return {\n height: result.height,\n lineCount: result.lineCount,\n }\n }\n\n /**\n * Compute layout for code blocks using code font.\n * Falls back to main font if codeFont is not configured.\n */\n computeCodeLayout(text: string): LayoutResult {\n const font = this.config.codeFont ?? this.config.font\n const lineHeight = this.config.codeLineHeight ?? this.config.lineHeight\n const key = `${font}|pre-wrap|${text}`\n let prepared = this.preparedCache.get(key)\n if (!prepared) {\n prepared = this.backend.prepare(text, font, { whiteSpace: 'pre-wrap' })\n this.preparedCache.set(key, prepared)\n }\n const result = this.backend.layout(prepared, this.config.maxWidth, lineHeight)\n return {\n height: result.height,\n lineCount: result.lineCount,\n }\n }\n\n /**\n * Compute layout with individual line information.\n * Uses prepareWithSegments() + layoutWithLines().\n */\n computeLayoutWithLines(text: string): LayoutResult {\n const prepared = this.getPreparedWithSegments(text)\n const result = this.backend.layoutWithLines(\n prepared,\n this.config.maxWidth,\n this.config.lineHeight,\n )\n\n const lines: LayoutLine[] = result.lines.map((line, i) => ({\n text: line.text,\n width: line.width,\n y: i * this.config.lineHeight,\n sourceIndex: i,\n }))\n\n return {\n height: result.height,\n lineCount: result.lineCount,\n lines,\n }\n }\n\n /**\n * Compute layout for only the visible viewport.\n * Key performance optimization: only measure and position visible lines.\n * Includes configurable buffer (default 2x viewport) for smooth scrolling.\n */\n computeViewportLayout(\n text: string,\n scrollTop: number,\n viewportHeight: number,\n ): ViewportLayoutResult {\n const allLayout = this.computeLayoutWithLines(text)\n const allLines = allLayout.lines ?? []\n const { lineHeight } = this.config\n const buffer = (this.config.viewportBuffer ?? 2) * viewportHeight\n\n const bufferedTop = Math.max(0, scrollTop - buffer)\n const bufferedBottom = scrollTop + viewportHeight + buffer\n\n const startIndex = Math.max(0, Math.floor(bufferedTop / lineHeight))\n const endIndex = Math.min(allLines.length, Math.ceil(bufferedBottom / lineHeight))\n\n const visibleLines = allLines.slice(startIndex, endIndex)\n\n return {\n visibleLines,\n totalHeight: allLayout.height,\n startY: startIndex * lineHeight,\n startIndex,\n endIndex,\n }\n }\n\n // --------------------------------------------------------\n // Multi-paragraph Layout\n // --------------------------------------------------------\n\n /**\n * Compute layout for an array of text blocks (paragraphs).\n * Returns cumulative heights for virtual scrolling.\n */\n computeDocumentLayout(paragraphs: string[]): {\n totalHeight: number\n paragraphOffsets: number[]\n paragraphHeights: number[]\n } {\n const offsets: number[] = []\n const heights: number[] = []\n let cumHeight = 0\n\n for (const text of paragraphs) {\n offsets.push(cumHeight)\n const result = this.computeLayout(text)\n heights.push(result.height)\n cumHeight += result.height\n }\n\n return {\n totalHeight: cumHeight,\n paragraphOffsets: offsets,\n paragraphHeights: heights,\n }\n }\n\n /**\n * Find which paragraph and line is at a given scrollTop position.\n */\n hitTest(\n paragraphs: string[],\n scrollTop: number,\n ): { paragraphIndex: number; lineIndex: number } | null {\n let cumHeight = 0\n for (let i = 0; i < paragraphs.length; i++) {\n const result = this.computeLayout(paragraphs[i]!)\n if (cumHeight + result.height > scrollTop) {\n const localY = scrollTop - cumHeight\n const lineIndex = Math.floor(localY / this.config.lineHeight)\n return { paragraphIndex: i, lineIndex }\n }\n cumHeight += result.height\n }\n return null\n }\n\n // --------------------------------------------------------\n // Incremental Document Layout\n // --------------------------------------------------------\n\n private _lastParagraphs: string[] = []\n private _lastHeights: number[] = []\n private _lastTotalHeight = 0\n\n /**\n * Incremental document layout — reuses cached heights for unchanged paragraphs.\n * Only recomputes layout for paragraphs that changed since the last call.\n * Use this for real-time editing instead of computeDocumentLayout().\n *\n * Returns the same structure as computeDocumentLayout().\n */\n updateDocumentLayout(paragraphs: string[]): {\n totalHeight: number\n paragraphOffsets: number[]\n paragraphHeights: number[]\n changedIndices: number[]\n } {\n const prev = this._lastParagraphs\n const prevHeights = this._lastHeights\n const len = paragraphs.length\n const heights: number[] = new Array(len)\n const offsets: number[] = new Array(len)\n const changedIndices: number[] = []\n let cumHeight = 0\n\n for (let i = 0; i < len; i++) {\n const text = paragraphs[i]!\n // Reuse if same paragraph text\n if (i < prev.length && prev[i] === text) {\n heights[i] = prevHeights[i]!\n } else {\n heights[i] = this.computeLayout(text).height\n changedIndices.push(i)\n }\n offsets[i] = cumHeight\n cumHeight += heights[i]!\n }\n\n // Track for next call\n this._lastParagraphs = paragraphs\n this._lastHeights = heights\n this._lastTotalHeight = cumHeight\n\n return {\n totalHeight: cumHeight,\n paragraphOffsets: offsets,\n paragraphHeights: heights,\n changedIndices,\n }\n }\n\n /**\n * Get the cached total height from the last updateDocumentLayout() call.\n * O(1) — no recomputation.\n */\n getCachedTotalHeight(): number {\n return this._lastTotalHeight\n }\n\n // --------------------------------------------------------\n // Cache Management\n // --------------------------------------------------------\n\n /** Invalidate cache for a specific text block. */\n invalidateCache(text?: string): void {\n if (text) {\n const key = this.cacheKey(text)\n this.preparedCache.delete(key)\n this.preparedSegCache.delete(key)\n } else {\n this.clearAllCaches()\n }\n }\n\n /** Clear all caches including pretext internal cache. */\n clearAllCaches(): void {\n this.preparedCache.clear()\n this.preparedSegCache.clear()\n this.backend.clearCache()\n }\n\n /** Get current cache statistics. */\n getCacheStats(): { preparedSize: number; segmentSize: number } {\n return {\n preparedSize: this.preparedCache.size,\n segmentSize: this.preparedSegCache.size,\n }\n }\n\n // --------------------------------------------------------\n // Internal Helpers\n // --------------------------------------------------------\n\n private cacheKey(text: string): string {\n return `${this.config.font}|${this.config.whiteSpace ?? 'normal'}|${text}`\n }\n\n private getPrepared(text: string): PreparedText {\n const key = this.cacheKey(text)\n let prepared = this.preparedCache.get(key)\n if (!prepared) {\n const opts: PrepareOptions | undefined =\n this.config.whiteSpace === 'pre-wrap' ? { whiteSpace: 'pre-wrap' } : undefined\n prepared = this.backend.prepare(text, this.config.font, opts)\n this.preparedCache.set(key, prepared)\n }\n return prepared\n }\n\n private getPreparedWithSegments(text: string): PreparedTextWithSegments {\n const key = this.cacheKey(text)\n let prepared = this.preparedSegCache.get(key)\n if (!prepared) {\n const opts: PrepareOptions | undefined =\n this.config.whiteSpace === 'pre-wrap' ? { whiteSpace: 'pre-wrap' } : undefined\n prepared = this.backend.prepareWithSegments(text, this.config.font, opts)\n this.preparedSegCache.set(key, prepared)\n }\n return prepared\n }\n}\n\n// ============================================================\n// Worker Backend (Web Worker offloading for large documents)\n// ============================================================\n\nexport { createWorkerBackend } from './worker-backend.js'\nexport type { WorkerMeasurementBackend } from './worker-backend.js'\n\n// ============================================================\n// Virtual List (dynamic-height virtual scrolling)\n// ============================================================\n\nexport { VirtualList } from './virtual-list.js'\nexport type {\n VirtualListConfig,\n VirtualListItem,\n ViewportRange,\n ViewportChangeCallback,\n} from './virtual-list.js'\n\n// ============================================================\n// Cursor & Selection Engine (pretext-based cursor positioning)\n// ============================================================\n\nexport { CursorEngine } from './cursor.js'\nexport type {\n Point,\n Rect,\n CursorPosition,\n VisualLineInfo,\n LineNumberInfo,\n} from './cursor.js'\n\n// ============================================================\n// Line Renderer (pretext-based line number rendering)\n// ============================================================\n\nexport { LineRenderer } from './line-renderer.js'\nexport type {\n LineRendererConfig,\n RenderedLineNumber,\n} from './line-renderer.js'\n","/**\n * @pre-markdown/layout — Worker-Based Measurement Backend\n *\n * Offloads expensive pretext prepare() calls to a Web Worker,\n * keeping the main thread free for rendering and user interaction.\n *\n * The backend transparently proxies prepare()/prepareWithSegments() to the\n * worker thread and caches results on the main thread.\n *\n * Usage:\n * const backend = createWorkerBackend()\n * const engine = new LayoutEngine(config, backend)\n *\n * // Bulk prepare paragraphs in background (non-blocking)\n * await backend.prepareAsync(texts, font)\n *\n * // After prepareAsync, computeLayout() hits cache — synchronous & instant\n * engine.computeLayout(text)\n *\n * // Clean up when done\n * backend.terminate()\n */\n\nimport {\n prepare,\n prepareWithSegments,\n layout,\n layoutWithLines,\n clearCache as pretextClearCache,\n setLocale as pretextSetLocale,\n} from '@chenglou/pretext'\n\nimport type {\n PreparedText,\n PreparedTextWithSegments,\n LayoutResult as PretextLayoutResult,\n LayoutLinesResult as PretextLinesResult,\n PrepareOptions,\n} from '@chenglou/pretext'\n\nimport type { MeasurementBackend } from './index.js'\nimport type { WorkerRequest, WorkerResponse } from './worker-script.js'\n\n// ============================================================\n// Worker Backend Interface\n// ============================================================\n\nexport interface WorkerMeasurementBackend extends MeasurementBackend {\n /**\n * Asynchronously prepare multiple text blocks in the Worker thread.\n * Results are cached so subsequent synchronous prepare() calls are instant.\n *\n * Use this for bulk preparation of large documents:\n * await backend.prepareAsync(paragraphs, font)\n * // Now all paragraphs are cached, computeLayout() is O(1)\n */\n prepareAsync(texts: string[], font: string, options?: PrepareOptions): Promise<PreparedText[]>\n\n /**\n * Asynchronously prepare with segments (for layoutWithLines).\n */\n prepareWithSegmentsAsync(\n texts: string[],\n font: string,\n options?: PrepareOptions,\n ): Promise<PreparedTextWithSegments[]>\n\n /**\n * Check if the worker is alive.\n */\n readonly isAlive: boolean\n\n /**\n * Terminate the worker. After this, async methods will reject.\n * Synchronous methods fall back to main-thread pretext.\n */\n terminate(): void\n}\n\n// ============================================================\n// Implementation\n// ============================================================\n\n/**\n * Create a Worker-based measurement backend.\n *\n * On the main thread:\n * - prepare() / prepareWithSegments() — synchronous, uses main-thread pretext (with cache)\n * - prepareAsync() / prepareWithSegmentsAsync() — offloads to Worker\n * - layout() / layoutWithLines() — always synchronous (pure arithmetic)\n *\n * The typical workflow:\n * 1. Call prepareAsync() for bulk paragraphs → Worker runs prepare() in background\n * 2. Results are stored in main-thread cache\n * 3. Subsequent computeLayout() hits cache → no main-thread blocking\n *\n * @param workerUrl - Optional custom URL for the worker script.\n * Defaults to auto-resolving the bundled worker.\n */\nexport function createWorkerBackend(workerUrl?: URL | string): WorkerMeasurementBackend {\n // Cache for prepared results (key → PreparedText)\n const preparedCache = new Map<string, PreparedText>()\n const segmentCache = new Map<string, PreparedTextWithSegments>()\n\n // Worker setup\n let worker: Worker | null = null\n let alive = true\n let nextId = 1\n const pending = new Map<number, {\n resolve: (value: unknown) => void\n reject: (reason: unknown) => void\n }>()\n\n function getWorker(): Worker {\n if (worker) return worker\n\n const url = workerUrl\n ?? new URL('./worker-script.js', import.meta.url)\n\n worker = new Worker(url, { type: 'module' })\n worker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n const { id, type, result, error } = event.data\n const handlers = pending.get(id)\n if (!handlers) return\n pending.delete(id)\n if (type === 'error') {\n handlers.reject(new Error(error ?? 'Worker error'))\n } else {\n handlers.resolve(result)\n }\n }\n worker.onerror = (event) => {\n // Reject all pending requests\n for (const [id, handlers] of pending) {\n handlers.reject(new Error(`Worker error: ${event.message}`))\n }\n pending.clear()\n }\n return worker\n }\n\n function sendMessage(msg: Omit<WorkerRequest, 'id'>): Promise<unknown> {\n if (!alive) return Promise.reject(new Error('Worker has been terminated'))\n return new Promise((resolve, reject) => {\n const id = nextId++\n pending.set(id, { resolve, reject })\n getWorker().postMessage({ ...msg, id } as WorkerRequest)\n })\n }\n\n function cacheKey(text: string, font: string, options?: PrepareOptions): string {\n return `${font}|${options?.whiteSpace ?? 'normal'}|${text}`\n }\n\n // --------------------------------------------------------\n // Backend Implementation\n // --------------------------------------------------------\n\n const backend: WorkerMeasurementBackend = {\n // Synchronous prepare — uses main-thread pretext (with cache fallback)\n prepare(text: string, font: string, options?: PrepareOptions): PreparedText {\n const key = cacheKey(text, font, options)\n let cached = preparedCache.get(key)\n if (!cached) {\n cached = prepare(text, font, options)\n preparedCache.set(key, cached)\n }\n return cached\n },\n\n prepareWithSegments(\n text: string,\n font: string,\n options?: PrepareOptions,\n ): PreparedTextWithSegments {\n const key = cacheKey(text, font, options)\n let cached = segmentCache.get(key)\n if (!cached) {\n cached = prepareWithSegments(text, font, options)\n segmentCache.set(key, cached)\n }\n return cached\n },\n\n // Layout is always synchronous (pure arithmetic)\n layout(\n prepared: PreparedText,\n maxWidth: number,\n lineHeight: number,\n ): PretextLayoutResult {\n return layout(prepared, maxWidth, lineHeight)\n },\n\n layoutWithLines(\n prepared: PreparedTextWithSegments,\n maxWidth: number,\n lineHeight: number,\n ): PretextLinesResult {\n return layoutWithLines(prepared, maxWidth, lineHeight)\n },\n\n clearCache(): void {\n preparedCache.clear()\n segmentCache.clear()\n pretextClearCache()\n // Also clear worker's cache\n if (alive && worker) {\n sendMessage({ type: 'clearCache' }).catch(() => {})\n }\n },\n\n setLocale(locale?: string): void {\n pretextSetLocale(locale)\n if (alive && worker) {\n sendMessage({ type: 'setLocale', locale }).catch(() => {})\n }\n },\n\n // --------------------------------------------------------\n // Async API — offloads to Worker\n // --------------------------------------------------------\n\n async prepareAsync(\n texts: string[],\n font: string,\n options?: PrepareOptions,\n ): Promise<PreparedText[]> {\n const results: PreparedText[] = new Array(texts.length)\n const toProcess: { index: number; text: string }[] = []\n\n // Check cache first\n for (let i = 0; i < texts.length; i++) {\n const key = cacheKey(texts[i]!, font, options)\n const cached = preparedCache.get(key)\n if (cached) {\n results[i] = cached\n } else {\n toProcess.push({ index: i, text: texts[i]! })\n }\n }\n\n // Process uncached items in batches via Worker\n if (toProcess.length > 0) {\n const BATCH_SIZE = 50\n for (let b = 0; b < toProcess.length; b += BATCH_SIZE) {\n const batch = toProcess.slice(b, b + BATCH_SIZE)\n const promises = batch.map(({ text }) =>\n sendMessage({ type: 'prepare', text, font, options })\n .then((result) => result as PreparedText)\n )\n const batchResults = await Promise.all(promises)\n\n for (let j = 0; j < batch.length; j++) {\n const { index, text } = batch[j]!\n const prepared = batchResults[j]!\n const key = cacheKey(text, font, options)\n preparedCache.set(key, prepared)\n results[index] = prepared\n }\n }\n }\n\n return results\n },\n\n async prepareWithSegmentsAsync(\n texts: string[],\n font: string,\n options?: PrepareOptions,\n ): Promise<PreparedTextWithSegments[]> {\n const results: PreparedTextWithSegments[] = new Array(texts.length)\n const toProcess: { index: number; text: string }[] = []\n\n // Check cache first\n for (let i = 0; i < texts.length; i++) {\n const key = cacheKey(texts[i]!, font, options)\n const cached = segmentCache.get(key)\n if (cached) {\n results[i] = cached\n } else {\n toProcess.push({ index: i, text: texts[i]! })\n }\n }\n\n // Process uncached items\n if (toProcess.length > 0) {\n const BATCH_SIZE = 50\n for (let b = 0; b < toProcess.length; b += BATCH_SIZE) {\n const batch = toProcess.slice(b, b + BATCH_SIZE)\n const promises = batch.map(({ text }) =>\n sendMessage({ type: 'prepareWithSegments', text, font, options })\n .then((result) => result as PreparedTextWithSegments)\n )\n const batchResults = await Promise.all(promises)\n\n for (let j = 0; j < batch.length; j++) {\n const { index, text } = batch[j]!\n const prepared = batchResults[j]!\n const key = cacheKey(text, font, options)\n segmentCache.set(key, prepared)\n results[index] = prepared\n }\n }\n }\n\n return results\n },\n\n get isAlive() {\n return alive\n },\n\n terminate(): void {\n alive = false\n if (worker) {\n worker.terminate()\n worker = null\n }\n // Reject all pending requests\n for (const [, handlers] of pending) {\n handlers.reject(new Error('Worker terminated'))\n }\n pending.clear()\n },\n }\n\n return backend\n}\n","/**\n * @pre-markdown/layout — Virtual List\n *\n * Dynamic-height virtual list powered by pretext precise text measurement.\n * Unlike typical virtual lists that estimate item heights, this uses\n * pretext's exact layout computation for pixel-perfect scrolling.\n *\n * Key features:\n * - Zero estimation error: every item height is precisely computed via pretext\n * - Incremental updates: only recompute changed items\n * - Binary search viewport: O(log n) item lookup\n * - Configurable overscan: smooth scrolling with buffer items\n * - Resize-aware: automatic relayout on width changes\n * - Callback-driven: onViewportChange fires with visible item range\n */\n\nimport type { LayoutEngine, LayoutResult } from './index.js'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface VirtualListConfig {\n /** The LayoutEngine instance for text measurement */\n engine: LayoutEngine\n /** Viewport height in pixels */\n viewportHeight: number\n /** Number of extra items to render above/below viewport (default: 5) */\n overscan?: number\n /** Gap between items in pixels (default: 0) */\n itemGap?: number\n}\n\nexport interface VirtualListItem {\n /** Index in the data array */\n index: number\n /** Y offset from document top (pixels) */\n offsetTop: number\n /** Item height in pixels (pretext-measured) */\n height: number\n /** The text content */\n text: string\n}\n\nexport interface ViewportRange {\n /** First visible item index (inclusive) */\n startIndex: number\n /** Last visible item index (exclusive) */\n endIndex: number\n /** Items to render (including overscan) */\n items: VirtualListItem[]\n /** Total scroll height of all items */\n totalHeight: number\n /** Offset to position the visible container */\n offsetY: number\n}\n\nexport type ViewportChangeCallback = (range: ViewportRange) => void\n\n// ============================================================\n// VirtualList\n// ============================================================\n\nexport class VirtualList {\n private engine: LayoutEngine\n private viewportHeight: number\n private overscan: number\n private itemGap: number\n\n /** Item texts */\n private items: string[] = []\n /** Cached item heights (pretext-measured) */\n private heights: number[] = []\n /** Cumulative offsets (heights[0..i-1] + gaps) */\n private offsets: number[] = []\n /** Total height of all items */\n private totalHeight = 0\n\n /** Current scroll position */\n private scrollTop = 0\n /** Viewport change callback */\n private onViewportChange: ViewportChangeCallback | null = null\n\n constructor(config: VirtualListConfig) {\n this.engine = config.engine\n this.viewportHeight = config.viewportHeight\n this.overscan = config.overscan ?? 5\n this.itemGap = config.itemGap ?? 0\n }\n\n // --------------------------------------------------------\n // Data Management\n // --------------------------------------------------------\n\n /**\n * Set the full list of items. Computes all heights.\n * For incremental updates, use updateItems() instead.\n */\n setItems(texts: string[]): void {\n this.items = texts\n this.heights = new Array(texts.length)\n this.offsets = new Array(texts.length)\n\n let cumHeight = 0\n for (let i = 0; i < texts.length; i++) {\n this.offsets[i] = cumHeight\n const result = this.engine.computeLayout(texts[i]!)\n this.heights[i] = result.height\n cumHeight += result.height + this.itemGap\n }\n // Subtract the last gap (no gap after last item)\n this.totalHeight = texts.length > 0 ? cumHeight - this.itemGap : 0\n }\n\n /**\n * Incrementally update items. Only recomputes heights for changed items.\n * Much faster than setItems() for editing scenarios.\n *\n * @returns Indices of items that changed height\n */\n updateItems(texts: string[]): number[] {\n const prevItems = this.items\n const prevHeights = this.heights\n this.items = texts\n const len = texts.length\n const heights: number[] = new Array(len)\n const changedIndices: number[] = []\n\n for (let i = 0; i < len; i++) {\n if (i < prevItems.length && prevItems[i] === texts[i]) {\n heights[i] = prevHeights[i]!\n } else {\n heights[i] = this.engine.computeLayout(texts[i]!).height\n changedIndices.push(i)\n }\n }\n\n this.heights = heights\n\n // Rebuild offsets\n this.offsets = new Array(len)\n let cumHeight = 0\n for (let i = 0; i < len; i++) {\n this.offsets[i] = cumHeight\n cumHeight += heights[i]! + this.itemGap\n }\n this.totalHeight = len > 0 ? cumHeight - this.itemGap : 0\n\n return changedIndices\n }\n\n /**\n * Get the height of a specific item.\n */\n getItemHeight(index: number): number {\n return this.heights[index] ?? 0\n }\n\n /**\n * Get the offset of a specific item from the top.\n */\n getItemOffset(index: number): number {\n return this.offsets[index] ?? 0\n }\n\n // --------------------------------------------------------\n // Scroll & Viewport\n // --------------------------------------------------------\n\n /**\n * Set the scroll position and compute the visible range.\n * Fires the onViewportChange callback if registered.\n */\n setScrollTop(scrollTop: number): ViewportRange {\n this.scrollTop = Math.max(0, Math.min(scrollTop, this.totalHeight - this.viewportHeight))\n const range = this.computeViewport()\n if (this.onViewportChange) {\n this.onViewportChange(range)\n }\n return range\n }\n\n /**\n * Get the current scroll position.\n */\n getScrollTop(): number {\n return this.scrollTop\n }\n\n /**\n * Update viewport height (e.g., on window resize).\n */\n setViewportHeight(height: number): void {\n this.viewportHeight = height\n }\n\n /**\n * Scroll to make a specific item visible.\n * @param index - Item index to scroll to\n * @param align - 'start' | 'center' | 'end' (default: 'start')\n */\n scrollToItem(index: number, align: 'start' | 'center' | 'end' = 'start'): ViewportRange {\n if (index < 0 || index >= this.items.length) {\n return this.computeViewport()\n }\n\n const offset = this.offsets[index]!\n const height = this.heights[index]!\n\n let scrollTop: number\n switch (align) {\n case 'start':\n scrollTop = offset\n break\n case 'center':\n scrollTop = offset - (this.viewportHeight - height) / 2\n break\n case 'end':\n scrollTop = offset - this.viewportHeight + height\n break\n }\n\n return this.setScrollTop(scrollTop)\n }\n\n /**\n * Register a callback for viewport changes.\n */\n onViewport(callback: ViewportChangeCallback | null): void {\n this.onViewportChange = callback\n }\n\n // --------------------------------------------------------\n // Viewport Computation\n // --------------------------------------------------------\n\n /**\n * Compute the current visible range.\n */\n computeViewport(): ViewportRange {\n const len = this.items.length\n if (len === 0) {\n return {\n startIndex: 0,\n endIndex: 0,\n items: [],\n totalHeight: 0,\n offsetY: 0,\n }\n }\n\n const scrollTop = this.scrollTop\n const scrollBottom = scrollTop + this.viewportHeight\n\n // Binary search for the first visible item\n let startIndex = this.binarySearchOffset(scrollTop)\n // Binary search for the last visible item\n let endIndex = this.binarySearchOffset(scrollBottom)\n\n // Ensure endIndex is past the last partially visible item\n if (endIndex < len) endIndex++\n\n // Apply overscan\n startIndex = Math.max(0, startIndex - this.overscan)\n endIndex = Math.min(len, endIndex + this.overscan)\n\n // Build visible items\n const items: VirtualListItem[] = []\n for (let i = startIndex; i < endIndex; i++) {\n items.push({\n index: i,\n offsetTop: this.offsets[i]!,\n height: this.heights[i]!,\n text: this.items[i]!,\n })\n }\n\n return {\n startIndex,\n endIndex,\n items,\n totalHeight: this.totalHeight,\n offsetY: this.offsets[startIndex] ?? 0,\n }\n }\n\n // --------------------------------------------------------\n // Hit Testing\n // --------------------------------------------------------\n\n /**\n * Find which item is at a given Y position.\n * @returns Item index, or -1 if out of bounds\n */\n hitTest(y: number): number {\n if (this.items.length === 0 || y < 0) return -1\n if (y >= this.totalHeight) return -1\n return this.binarySearchOffset(y)\n }\n\n /**\n * Find the item and local Y offset within it.\n */\n hitTestDetailed(y: number): { index: number; localY: number } | null {\n const index = this.hitTest(y)\n if (index === -1) return null\n return {\n index,\n localY: y - this.offsets[index]!,\n }\n }\n\n // --------------------------------------------------------\n // Resize Handling\n // --------------------------------------------------------\n\n /**\n * Recompute all item heights (e.g., after maxWidth change).\n * Call this after engine.updateConfig({ maxWidth: newWidth }).\n *\n * @returns Time taken in milliseconds\n */\n relayout(): number {\n const start = performance.now()\n\n let cumHeight = 0\n for (let i = 0; i < this.items.length; i++) {\n this.offsets[i] = cumHeight\n const result = this.engine.computeLayout(this.items[i]!)\n this.heights[i] = result.height\n cumHeight += result.height + this.itemGap\n }\n this.totalHeight = this.items.length > 0 ? cumHeight - this.itemGap : 0\n\n return performance.now() - start\n }\n\n // --------------------------------------------------------\n // Getters\n // --------------------------------------------------------\n\n /** Total height of all items */\n getTotalHeight(): number {\n return this.totalHeight\n }\n\n /** Number of items */\n getItemCount(): number {\n return this.items.length\n }\n\n /** Get all item heights (readonly) */\n getHeights(): readonly number[] {\n return this.heights\n }\n\n /** Get all item offsets (readonly) */\n getOffsets(): readonly number[] {\n return this.offsets\n }\n\n // --------------------------------------------------------\n // Internal: Binary Search\n // --------------------------------------------------------\n\n /**\n * Binary search for the item at a given Y offset.\n * Returns the index of the item that contains the offset.\n */\n private binarySearchOffset(y: number): number {\n const offsets = this.offsets\n const len = offsets.length\n if (len === 0) return 0\n\n let lo = 0\n let hi = len - 1\n\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1\n if (offsets[mid]! <= y) {\n lo = mid\n } else {\n hi = mid - 1\n }\n }\n\n return lo\n }\n}\n","/**\n * @pre-markdown/layout — Cursor & Selection Engine\n *\n * Provides pixel-perfect cursor positioning and selection highlighting\n * powered by pretext's zero-DOM-reflow text measurement.\n *\n * Key capabilities:\n * - offsetToXY: text offset → (x, y) pixel coordinates (via pretext layout)\n * - xyToOffset: (x, y) click → text offset (reverse hit-test)\n * - getSelectionRects: start/end offset → array of highlight rectangles\n * - getLineInfo: get line boundaries, count, and visual line mapping\n *\n * All calculations are pure arithmetic after initial prepare() —\n * zero DOM reads, zero reflow.\n */\n\nimport type { LayoutEngine, LayoutLine } from './index.js'\n\n// ============================================================\n// Types\n// ============================================================\n\n/** A 2D point in pixel coordinates */\nexport interface Point {\n x: number\n y: number\n}\n\n/** A rectangle in pixel coordinates */\nexport interface Rect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** Cursor position information */\nexport interface CursorPosition {\n /** Text offset in the source string */\n offset: number\n /** Visual line index (0-based, accounts for wrapping) */\n visualLine: number\n /** X coordinate in pixels from left edge */\n x: number\n /** Y coordinate in pixels from top */\n y: number\n /** Line height in pixels */\n lineHeight: number\n}\n\n/** Information about a visual line (may be a wrapped sub-line) */\nexport interface VisualLineInfo {\n /** Visual line index (0-based) */\n index: number\n /** Text content of this visual line */\n text: string\n /** Measured width in pixels */\n width: number\n /** Y offset from document top */\n y: number\n /** The source (hard) line index (0-based) */\n sourceLine: number\n /** Start offset in the original text */\n startOffset: number\n /** End offset in the original text (exclusive) */\n endOffset: number\n}\n\n/** Line number mapping for display */\nexport interface LineNumberInfo {\n /** Source line number (1-based, for display) */\n lineNumber: number\n /** Y position of this source line's first visual line */\n y: number\n /** Number of visual lines this source line spans */\n visualLineCount: number\n /** Height of all visual lines for this source line */\n height: number\n}\n\n// ============================================================\n// CursorEngine\n// ============================================================\n\n/**\n * Cursor and selection engine powered by pretext.\n *\n * Usage:\n * const cursor = new CursorEngine(layoutEngine)\n * cursor.setText(editorContent)\n *\n * // Click → offset\n * const offset = cursor.xyToOffset(clickX, clickY)\n *\n * // Offset → position\n * const pos = cursor.offsetToPosition(offset)\n *\n * // Selection rectangles\n * const rects = cursor.getSelectionRects(selStart, selEnd)\n */\nexport class CursorEngine {\n private engine: LayoutEngine\n private text = ''\n private hardLines: string[] = ['']\n private visualLines: VisualLineInfo[] = []\n private lineNumbers: LineNumberInfo[] = []\n private totalHeight = 0\n\n constructor(engine: LayoutEngine) {\n this.engine = engine\n }\n\n // --------------------------------------------------------\n // Text Management\n // --------------------------------------------------------\n\n /**\n * Set the text content and compute all visual line info.\n * Call this when the editor content changes.\n */\n setText(text: string): void {\n this.text = text\n this.hardLines = text.split('\\n')\n this.recompute()\n }\n\n /** Get current text */\n getText(): string {\n return this.text\n }\n\n /**\n * Force recomputation of layout (e.g., after width change).\n * Call after engine.updateConfig({ maxWidth: newWidth }).\n */\n recompute(): void {\n const { hardLines } = this\n const visualLines: VisualLineInfo[] = []\n const lineNumbers: LineNumberInfo[] = []\n const lineHeight = this.engine.getConfig().lineHeight\n\n let globalOffset = 0 // character offset in original text\n let y = 0\n\n for (let srcLine = 0; srcLine < hardLines.length; srcLine++) {\n const lineText = hardLines[srcLine]!\n const layout = this.engine.computeLayoutWithLines(lineText)\n const lines = layout.lines ?? []\n\n const lineNumInfo: LineNumberInfo = {\n lineNumber: srcLine + 1,\n y,\n visualLineCount: lines.length || 1,\n height: layout.height || lineHeight,\n }\n lineNumbers.push(lineNumInfo)\n\n if (lines.length === 0) {\n // Empty line\n visualLines.push({\n index: visualLines.length,\n text: '',\n width: 0,\n y,\n sourceLine: srcLine,\n startOffset: globalOffset,\n endOffset: globalOffset,\n })\n y += lineHeight\n } else {\n let lineStartOffset = globalOffset\n for (let vl = 0; vl < lines.length; vl++) {\n const line = lines[vl]!\n const endOffset = lineStartOffset + line.text.length\n visualLines.push({\n index: visualLines.length,\n text: line.text,\n width: line.width,\n y,\n sourceLine: srcLine,\n startOffset: lineStartOffset,\n endOffset,\n })\n lineStartOffset = endOffset\n y += lineHeight\n }\n }\n\n // +1 for the '\\n' character (except last line)\n globalOffset += lineText.length\n if (srcLine < hardLines.length - 1) {\n globalOffset += 1 // '\\n'\n }\n }\n\n this.visualLines = visualLines\n this.lineNumbers = lineNumbers\n this.totalHeight = y\n }\n\n // --------------------------------------------------------\n // Cursor Positioning\n // --------------------------------------------------------\n\n /**\n * Convert a text offset to pixel coordinates.\n * Returns the cursor position (x, y) for rendering a blinking cursor.\n */\n offsetToPosition(offset: number): CursorPosition {\n const clamped = Math.max(0, Math.min(offset, this.text.length))\n const lineHeight = this.engine.getConfig().lineHeight\n\n // Find which visual line contains this offset\n const vl = this.findVisualLineByOffset(clamped)\n\n if (!vl) {\n return { offset: clamped, visualLine: 0, x: 0, y: 0, lineHeight }\n }\n\n // Compute X by measuring the substring up to the offset\n const localOffset = clamped - vl.startOffset\n const prefix = vl.text.slice(0, localOffset)\n\n let x = 0\n if (prefix.length > 0) {\n const prefixLayout = this.engine.computeLayout(prefix)\n // For single-line prefix, width ≈ height / lineHeight * avgCharWidth\n // But more accurately, use computeLayoutWithLines\n const prefixLines = this.engine.computeLayoutWithLines(prefix)\n const lastLine = prefixLines.lines?.[prefixLines.lines.length - 1]\n x = lastLine?.width ?? 0\n }\n\n return {\n offset: clamped,\n visualLine: vl.index,\n x,\n y: vl.y,\n lineHeight,\n }\n }\n\n /**\n * Convert pixel coordinates (from a click event) to a text offset.\n * This is the reverse of offsetToPosition().\n */\n xyToOffset(x: number, y: number): number {\n const lineHeight = this.engine.getConfig().lineHeight\n\n // Find which visual line was clicked\n const visualLineIdx = Math.floor(y / lineHeight)\n const vl = this.visualLines[Math.max(0, Math.min(visualLineIdx, this.visualLines.length - 1))]\n\n if (!vl) return 0\n\n // Binary search for the character at x position\n const lineText = vl.text\n if (lineText.length === 0) return vl.startOffset\n\n // Binary search: find the offset where measured width crosses x\n let lo = 0\n let hi = lineText.length\n\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n const prefix = lineText.slice(0, mid + 1)\n const layout = this.engine.computeLayoutWithLines(prefix)\n const lastLine = layout.lines?.[layout.lines.length - 1]\n const w = lastLine?.width ?? 0\n\n if (w <= x) {\n lo = mid + 1\n } else {\n hi = mid\n }\n }\n\n // Fine-tune: check if click is closer to lo or lo-1\n if (lo > 0 && lo <= lineText.length) {\n const prefixPrev = lineText.slice(0, lo - 1)\n const prefixCurr = lineText.slice(0, lo)\n const layoutPrev = this.engine.computeLayoutWithLines(prefixPrev)\n const layoutCurr = this.engine.computeLayoutWithLines(prefixCurr)\n const wPrev = layoutPrev.lines?.[layoutPrev.lines.length - 1]?.width ?? 0\n const wCurr = layoutCurr.lines?.[layoutCurr.lines.length - 1]?.width ?? 0\n\n if (x - wPrev < wCurr - x) {\n lo = lo - 1\n }\n }\n\n return vl.startOffset + Math.min(lo, lineText.length)\n }\n\n // --------------------------------------------------------\n // Selection\n // --------------------------------------------------------\n\n /**\n * Compute selection highlight rectangles for a text range.\n * Returns one rect per visual line that intersects the selection.\n */\n getSelectionRects(start: number, end: number): Rect[] {\n if (start === end) return []\n const s = Math.min(start, end)\n const e = Math.max(start, end)\n const lineHeight = this.engine.getConfig().lineHeight\n const maxWidth = this.engine.getConfig().maxWidth\n const rects: Rect[] = []\n\n for (const vl of this.visualLines) {\n // Skip lines outside selection\n if (vl.endOffset <= s) continue\n if (vl.startOffset >= e) break\n\n // Compute the intersection\n const selStart = Math.max(s, vl.startOffset)\n const selEnd = Math.min(e, vl.endOffset)\n\n // Measure x coordinates\n const localStart = selStart - vl.startOffset\n const localEnd = selEnd - vl.startOffset\n\n let x1 = 0\n if (localStart > 0) {\n const prefix = vl.text.slice(0, localStart)\n const layout = this.engine.computeLayoutWithLines(prefix)\n x1 = layout.lines?.[layout.lines.length - 1]?.width ?? 0\n }\n\n let x2: number\n if (localEnd >= vl.text.length) {\n // Selection extends to end of line — extend to maxWidth for visual clarity\n x2 = vl.text.length > 0 ? vl.width : 0\n // If selection spans to next line, extend rect to fill the line\n if (selEnd > vl.endOffset || (selEnd === vl.endOffset && e > vl.endOffset)) {\n x2 = Math.max(x2, maxWidth)\n }\n } else {\n const prefix = vl.text.slice(0, localEnd)\n const layout = this.engine.computeLayoutWithLines(prefix)\n x2 = layout.lines?.[layout.lines.length - 1]?.width ?? 0\n }\n\n if (x2 > x1 || (x1 === 0 && x2 === 0 && selStart < selEnd)) {\n rects.push({\n x: x1,\n y: vl.y,\n width: Math.max(x2 - x1, 4), // min 4px for visibility on empty lines\n height: lineHeight,\n })\n }\n }\n\n return rects\n }\n\n // --------------------------------------------------------\n // Line Information\n // --------------------------------------------------------\n\n /** Get all visual lines */\n getVisualLines(): readonly VisualLineInfo[] {\n return this.visualLines\n }\n\n /** Get visual line count (total lines including wrapped) */\n getVisualLineCount(): number {\n return this.visualLines.length\n }\n\n /** Get source line count (hard lines from \\n) */\n getSourceLineCount(): number {\n return this.hardLines.length\n }\n\n /**\n * Get line number info for rendering line numbers.\n * Each entry represents a source line with its Y position\n * and how many visual lines it spans (for proper alignment).\n */\n getLineNumbers(): readonly LineNumberInfo[] {\n return this.lineNumbers\n }\n\n /** Get total content height in pixels */\n getTotalHeight(): number {\n return this.totalHeight\n }\n\n /**\n * Get the source line number for a given text offset.\n * Returns 1-based line number.\n */\n getLineNumberAtOffset(offset: number): number {\n const vl = this.findVisualLineByOffset(offset)\n return vl ? vl.sourceLine + 1 : 1\n }\n\n /**\n * Get the visual line at a given Y coordinate.\n */\n getVisualLineAtY(y: number): VisualLineInfo | null {\n const lineHeight = this.engine.getConfig().lineHeight\n const idx = Math.floor(y / lineHeight)\n return this.visualLines[idx] ?? null\n }\n\n /**\n * Get all visual lines for a given source line.\n */\n getVisualLinesForSourceLine(sourceLine: number): VisualLineInfo[] {\n return this.visualLines.filter(vl => vl.sourceLine === sourceLine)\n }\n\n // --------------------------------------------------------\n // Word boundary utilities (for double-click word selection)\n // --------------------------------------------------------\n\n /**\n * Find word boundaries at a given offset.\n * Returns [start, end] offsets of the word.\n */\n getWordBoundary(offset: number): [number, number] {\n const text = this.text\n if (text.length === 0) return [0, 0]\n const clamped = Math.max(0, Math.min(offset, text.length - 1))\n\n // Check if at a word character\n const isWordChar = (ch: number) =>\n (ch >= 0x30 && ch <= 0x39) || // 0-9\n (ch >= 0x41 && ch <= 0x5A) || // A-Z\n (ch >= 0x61 && ch <= 0x7A) || // a-z\n ch === 0x5F || // _\n ch > 0x7F // non-ASCII (CJK etc.)\n\n const ch = text.charCodeAt(clamped)\n if (!isWordChar(ch)) {\n // Not a word char — select just this character\n return [clamped, clamped + 1]\n }\n\n let start = clamped\n while (start > 0 && isWordChar(text.charCodeAt(start - 1))) start--\n\n let end = clamped\n while (end < text.length && isWordChar(text.charCodeAt(end))) end++\n\n return [start, end]\n }\n\n // --------------------------------------------------------\n // Internal helpers\n // --------------------------------------------------------\n\n /**\n * Find the visual line containing a given text offset.\n * Uses binary search for O(log n) performance.\n */\n private findVisualLineByOffset(offset: number): VisualLineInfo | null {\n const lines = this.visualLines\n if (lines.length === 0) return null\n\n let lo = 0\n let hi = lines.length - 1\n\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1\n if (lines[mid]!.startOffset <= offset) {\n lo = mid\n } else {\n hi = mid - 1\n }\n }\n\n return lines[lo] ?? null\n }\n}\n","/**\n * @pre-markdown/layout — Line Renderer\n *\n * Provides pretext-based line number rendering and soft-wrap computation.\n * Replaces naive \"one div per line\" approach with accurate visual line mapping\n * that correctly handles soft-wrapped (long) lines.\n *\n * Key features:\n * - Accurate line number positioning that aligns with soft-wrapped text\n * - Incremental updates: only recompute changed lines\n * - Active line tracking (current cursor line highlight)\n * - Virtual rendering for large documents (only render visible line numbers)\n * - Pure pretext computation — zero DOM measurement needed\n */\n\nimport type { LayoutEngine } from './index.js'\nimport type { CursorEngine, LineNumberInfo } from './cursor.js'\n\n// ============================================================\n// Types\n// ============================================================\n\n/** Configuration for the line renderer */\nexport interface LineRendererConfig {\n /** The CursorEngine instance (provides visual line mapping) */\n cursor: CursorEngine\n /** Container element for line numbers */\n container: HTMLElement\n /** Line height in pixels (must match editor) */\n lineHeight: number\n /** Class name for active line highlight */\n activeClass?: string\n /** Whether to use virtual rendering (default: true for > 1000 lines) */\n virtual?: boolean\n /** Extra lines to render above/below viewport in virtual mode */\n overscan?: number\n}\n\n/** A rendered line number entry */\nexport interface RenderedLineNumber {\n /** Source line number (1-based) */\n lineNumber: number\n /** Y position in pixels */\n y: number\n /** Height spanning all visual lines of this source line */\n height: number\n /** Whether this is the active (cursor) line */\n isActive: boolean\n}\n\n// ============================================================\n// LineRenderer\n// ============================================================\n\n/**\n * Renders line numbers with correct alignment for soft-wrapped text.\n * Uses pretext layout data from CursorEngine for pixel-perfect positioning.\n */\nexport class LineRenderer {\n private cursor: CursorEngine\n private container: HTMLElement\n private lineHeight: number\n private activeClass: string\n private virtual: boolean\n private overscan: number\n\n private activeLine = -1\n private lastScrollTop = 0\n private lastViewportHeight = 0\n private renderedRange: [number, number] = [0, 0]\n\n // DOM pool for virtual rendering\n private pool: HTMLDivElement[] = []\n\n constructor(config: LineRendererConfig) {\n this.cursor = config.cursor\n this.container = config.container\n this.lineHeight = config.lineHeight\n this.activeClass = config.activeClass ?? 'active-line'\n this.virtual = config.virtual ?? true\n this.overscan = config.overscan ?? 20\n }\n\n // --------------------------------------------------------\n // Active Line\n // --------------------------------------------------------\n\n /**\n * Set the active (cursor) line. Highlighted in the gutter.\n * @param lineNumber - 1-based source line number\n */\n setActiveLine(lineNumber: number): void {\n if (lineNumber === this.activeLine) return\n\n // Remove old highlight\n if (this.activeLine > 0) {\n const oldEl = this.container.querySelector(\n `[data-line=\"${this.activeLine}\"]`\n )\n if (oldEl) oldEl.classList.remove(this.activeClass)\n }\n\n this.activeLine = lineNumber\n\n // Add new highlight\n const newEl = this.container.querySelector(\n `[data-line=\"${lineNumber}\"]`\n )\n if (newEl) newEl.classList.add(this.activeClass)\n }\n\n /** Get the current active line number (1-based) */\n getActiveLine(): number {\n return this.activeLine\n }\n\n // --------------------------------------------------------\n // Rendering\n // --------------------------------------------------------\n\n /**\n * Full render: rebuild all line numbers.\n * Use for initial render or after text content changes.\n */\n render(): void {\n const lineNumbers = this.cursor.getLineNumbers()\n const totalLines = lineNumbers.length\n\n if (this.virtual && totalLines > 1000) {\n this.renderVirtual()\n return\n }\n\n // Non-virtual: render all line numbers\n this.renderAll(lineNumbers)\n }\n\n /**\n * Render all line numbers (non-virtual mode).\n * Uses absolutely-positioned divs for correct alignment with soft-wrapped text.\n */\n private renderAll(lineNumbers: readonly LineNumberInfo[]): void {\n const fragment = document.createDocumentFragment()\n\n for (const info of lineNumbers) {\n const div = document.createElement('div')\n div.textContent = String(info.lineNumber)\n div.setAttribute('data-line', String(info.lineNumber))\n div.style.height = info.height + 'px'\n div.style.lineHeight = info.height + 'px'\n\n if (info.lineNumber === this.activeLine) {\n div.classList.add(this.activeClass)\n }\n\n fragment.appendChild(div)\n }\n\n this.container.innerHTML = ''\n this.container.appendChild(fragment)\n }\n\n /**\n * Virtual rendering: only render line numbers visible in the viewport.\n * Uses absolute positioning with a spacer for correct scroll height.\n */\n renderVirtual(): void {\n const lineNumbers = this.cursor.getLineNumbers()\n const totalHeight = this.cursor.getTotalHeight()\n const scrollTop = this.lastScrollTop\n const viewportHeight = this.lastViewportHeight || 800\n\n // Find visible range\n const startY = Math.max(0, scrollTop - this.overscan * this.lineHeight)\n const endY = scrollTop + viewportHeight + this.overscan * this.lineHeight\n\n let startIdx = 0\n let endIdx = lineNumbers.length\n\n // Binary search for start\n {\n let lo = 0, hi = lineNumbers.length - 1\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1\n if (lineNumbers[mid]!.y <= startY) lo = mid\n else hi = mid - 1\n }\n startIdx = lo\n }\n\n // Binary search for end\n {\n let lo = startIdx, hi = lineNumbers.length - 1\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1\n if (lineNumbers[mid]!.y <= endY) lo = mid\n else hi = mid - 1\n }\n endIdx = lo + 1\n }\n\n // Only re-render if range changed\n if (this.renderedRange[0] === startIdx && this.renderedRange[1] === endIdx) {\n return\n }\n this.renderedRange = [startIdx, endIdx]\n\n // Build fragment\n this.container.innerHTML = ''\n\n // Spacer element for total height\n const spacer = document.createElement('div')\n spacer.style.height = totalHeight + 'px'\n spacer.style.position = 'relative'\n\n const fragment = document.createDocumentFragment()\n for (let i = startIdx; i < endIdx && i < lineNumbers.length; i++) {\n const info = lineNumbers[i]!\n const div = this.getPooledDiv()\n div.textContent = String(info.lineNumber)\n div.setAttribute('data-line', String(info.lineNumber))\n div.style.position = 'absolute'\n div.style.top = info.y + 'px'\n div.style.height = info.height + 'px'\n div.style.lineHeight = info.height + 'px'\n div.style.width = '100%'\n div.style.textAlign = 'right'\n\n if (info.lineNumber === this.activeLine) {\n div.classList.add(this.activeClass)\n } else {\n div.classList.remove(this.activeClass)\n }\n\n fragment.appendChild(div)\n }\n\n spacer.appendChild(fragment)\n this.container.appendChild(spacer)\n }\n\n /**\n * Update scroll position for virtual rendering.\n * Call from the editor's scroll event handler.\n */\n updateScroll(scrollTop: number, viewportHeight: number): void {\n this.lastScrollTop = scrollTop\n this.lastViewportHeight = viewportHeight\n\n if (this.virtual && this.cursor.getSourceLineCount() > 1000) {\n this.renderVirtual()\n }\n }\n\n /**\n * Update after text changes. Re-renders line numbers.\n */\n update(): void {\n this.render()\n }\n\n // --------------------------------------------------------\n // Auto-wrap information\n // --------------------------------------------------------\n\n /**\n * Get the number of visual lines for each source line.\n * Useful for understanding how text wraps without DOM measurement.\n *\n * @returns Array where index = source line (0-based), value = visual line count\n */\n getWrapInfo(): number[] {\n const lineNumbers = this.cursor.getLineNumbers()\n return lineNumbers.map(ln => ln.visualLineCount)\n }\n\n /**\n * Check if a source line is soft-wrapped (spans multiple visual lines).\n */\n isLineWrapped(sourceLine: number): boolean {\n const lineNumbers = this.cursor.getLineNumbers()\n const info = lineNumbers[sourceLine]\n return info ? info.visualLineCount > 1 : false\n }\n\n /**\n * Get total visual line count (including wrapped lines).\n */\n getTotalVisualLines(): number {\n return this.cursor.getVisualLineCount()\n }\n\n // --------------------------------------------------------\n // Cleanup\n // --------------------------------------------------------\n\n /**\n * Dispose resources.\n */\n dispose(): void {\n this.container.innerHTML = ''\n this.pool = []\n }\n\n // --------------------------------------------------------\n // Internal\n // --------------------------------------------------------\n\n private getPooledDiv(): HTMLDivElement {\n if (this.pool.length > 0) {\n return this.pool.pop()!\n }\n return document.createElement('div')\n }\n}\n"],"mappings":"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","names":["prepare","prepareWithSegments","layout","layoutWithLines","pretextClearCache","pretextSetLocale","layout","ch","prepare","prepareWithSegments","layout","layoutWithLines","pretextClearCache","pretextSetLocale"]}
|