@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
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
import { PrepareOptions, PreparedText, PreparedTextWithSegments, LayoutResult as LayoutResult$1, LayoutLinesResult } from '@chenglou/pretext';
|
|
2
|
+
export { PreparedText, PreparedTextWithSegments, LayoutLine as PretextLine } from '@chenglou/pretext';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @pre-markdown/layout — Worker-Based Measurement Backend
|
|
6
|
+
*
|
|
7
|
+
* Offloads expensive pretext prepare() calls to a Web Worker,
|
|
8
|
+
* keeping the main thread free for rendering and user interaction.
|
|
9
|
+
*
|
|
10
|
+
* The backend transparently proxies prepare()/prepareWithSegments() to the
|
|
11
|
+
* worker thread and caches results on the main thread.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* const backend = createWorkerBackend()
|
|
15
|
+
* const engine = new LayoutEngine(config, backend)
|
|
16
|
+
*
|
|
17
|
+
* // Bulk prepare paragraphs in background (non-blocking)
|
|
18
|
+
* await backend.prepareAsync(texts, font)
|
|
19
|
+
*
|
|
20
|
+
* // After prepareAsync, computeLayout() hits cache — synchronous & instant
|
|
21
|
+
* engine.computeLayout(text)
|
|
22
|
+
*
|
|
23
|
+
* // Clean up when done
|
|
24
|
+
* backend.terminate()
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
interface WorkerMeasurementBackend extends MeasurementBackend {
|
|
28
|
+
/**
|
|
29
|
+
* Asynchronously prepare multiple text blocks in the Worker thread.
|
|
30
|
+
* Results are cached so subsequent synchronous prepare() calls are instant.
|
|
31
|
+
*
|
|
32
|
+
* Use this for bulk preparation of large documents:
|
|
33
|
+
* await backend.prepareAsync(paragraphs, font)
|
|
34
|
+
* // Now all paragraphs are cached, computeLayout() is O(1)
|
|
35
|
+
*/
|
|
36
|
+
prepareAsync(texts: string[], font: string, options?: PrepareOptions): Promise<PreparedText[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Asynchronously prepare with segments (for layoutWithLines).
|
|
39
|
+
*/
|
|
40
|
+
prepareWithSegmentsAsync(texts: string[], font: string, options?: PrepareOptions): Promise<PreparedTextWithSegments[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Check if the worker is alive.
|
|
43
|
+
*/
|
|
44
|
+
readonly isAlive: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Terminate the worker. After this, async methods will reject.
|
|
47
|
+
* Synchronous methods fall back to main-thread pretext.
|
|
48
|
+
*/
|
|
49
|
+
terminate(): void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create a Worker-based measurement backend.
|
|
53
|
+
*
|
|
54
|
+
* On the main thread:
|
|
55
|
+
* - prepare() / prepareWithSegments() — synchronous, uses main-thread pretext (with cache)
|
|
56
|
+
* - prepareAsync() / prepareWithSegmentsAsync() — offloads to Worker
|
|
57
|
+
* - layout() / layoutWithLines() — always synchronous (pure arithmetic)
|
|
58
|
+
*
|
|
59
|
+
* The typical workflow:
|
|
60
|
+
* 1. Call prepareAsync() for bulk paragraphs → Worker runs prepare() in background
|
|
61
|
+
* 2. Results are stored in main-thread cache
|
|
62
|
+
* 3. Subsequent computeLayout() hits cache → no main-thread blocking
|
|
63
|
+
*
|
|
64
|
+
* @param workerUrl - Optional custom URL for the worker script.
|
|
65
|
+
* Defaults to auto-resolving the bundled worker.
|
|
66
|
+
*/
|
|
67
|
+
declare function createWorkerBackend(workerUrl?: URL | string): WorkerMeasurementBackend;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @pre-markdown/layout — Virtual List
|
|
71
|
+
*
|
|
72
|
+
* Dynamic-height virtual list powered by pretext precise text measurement.
|
|
73
|
+
* Unlike typical virtual lists that estimate item heights, this uses
|
|
74
|
+
* pretext's exact layout computation for pixel-perfect scrolling.
|
|
75
|
+
*
|
|
76
|
+
* Key features:
|
|
77
|
+
* - Zero estimation error: every item height is precisely computed via pretext
|
|
78
|
+
* - Incremental updates: only recompute changed items
|
|
79
|
+
* - Binary search viewport: O(log n) item lookup
|
|
80
|
+
* - Configurable overscan: smooth scrolling with buffer items
|
|
81
|
+
* - Resize-aware: automatic relayout on width changes
|
|
82
|
+
* - Callback-driven: onViewportChange fires with visible item range
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
interface VirtualListConfig {
|
|
86
|
+
/** The LayoutEngine instance for text measurement */
|
|
87
|
+
engine: LayoutEngine;
|
|
88
|
+
/** Viewport height in pixels */
|
|
89
|
+
viewportHeight: number;
|
|
90
|
+
/** Number of extra items to render above/below viewport (default: 5) */
|
|
91
|
+
overscan?: number;
|
|
92
|
+
/** Gap between items in pixels (default: 0) */
|
|
93
|
+
itemGap?: number;
|
|
94
|
+
}
|
|
95
|
+
interface VirtualListItem {
|
|
96
|
+
/** Index in the data array */
|
|
97
|
+
index: number;
|
|
98
|
+
/** Y offset from document top (pixels) */
|
|
99
|
+
offsetTop: number;
|
|
100
|
+
/** Item height in pixels (pretext-measured) */
|
|
101
|
+
height: number;
|
|
102
|
+
/** The text content */
|
|
103
|
+
text: string;
|
|
104
|
+
}
|
|
105
|
+
interface ViewportRange {
|
|
106
|
+
/** First visible item index (inclusive) */
|
|
107
|
+
startIndex: number;
|
|
108
|
+
/** Last visible item index (exclusive) */
|
|
109
|
+
endIndex: number;
|
|
110
|
+
/** Items to render (including overscan) */
|
|
111
|
+
items: VirtualListItem[];
|
|
112
|
+
/** Total scroll height of all items */
|
|
113
|
+
totalHeight: number;
|
|
114
|
+
/** Offset to position the visible container */
|
|
115
|
+
offsetY: number;
|
|
116
|
+
}
|
|
117
|
+
type ViewportChangeCallback = (range: ViewportRange) => void;
|
|
118
|
+
declare class VirtualList {
|
|
119
|
+
private engine;
|
|
120
|
+
private viewportHeight;
|
|
121
|
+
private overscan;
|
|
122
|
+
private itemGap;
|
|
123
|
+
/** Item texts */
|
|
124
|
+
private items;
|
|
125
|
+
/** Cached item heights (pretext-measured) */
|
|
126
|
+
private heights;
|
|
127
|
+
/** Cumulative offsets (heights[0..i-1] + gaps) */
|
|
128
|
+
private offsets;
|
|
129
|
+
/** Total height of all items */
|
|
130
|
+
private totalHeight;
|
|
131
|
+
/** Current scroll position */
|
|
132
|
+
private scrollTop;
|
|
133
|
+
/** Viewport change callback */
|
|
134
|
+
private onViewportChange;
|
|
135
|
+
constructor(config: VirtualListConfig);
|
|
136
|
+
/**
|
|
137
|
+
* Set the full list of items. Computes all heights.
|
|
138
|
+
* For incremental updates, use updateItems() instead.
|
|
139
|
+
*/
|
|
140
|
+
setItems(texts: string[]): void;
|
|
141
|
+
/**
|
|
142
|
+
* Incrementally update items. Only recomputes heights for changed items.
|
|
143
|
+
* Much faster than setItems() for editing scenarios.
|
|
144
|
+
*
|
|
145
|
+
* @returns Indices of items that changed height
|
|
146
|
+
*/
|
|
147
|
+
updateItems(texts: string[]): number[];
|
|
148
|
+
/**
|
|
149
|
+
* Get the height of a specific item.
|
|
150
|
+
*/
|
|
151
|
+
getItemHeight(index: number): number;
|
|
152
|
+
/**
|
|
153
|
+
* Get the offset of a specific item from the top.
|
|
154
|
+
*/
|
|
155
|
+
getItemOffset(index: number): number;
|
|
156
|
+
/**
|
|
157
|
+
* Set the scroll position and compute the visible range.
|
|
158
|
+
* Fires the onViewportChange callback if registered.
|
|
159
|
+
*/
|
|
160
|
+
setScrollTop(scrollTop: number): ViewportRange;
|
|
161
|
+
/**
|
|
162
|
+
* Get the current scroll position.
|
|
163
|
+
*/
|
|
164
|
+
getScrollTop(): number;
|
|
165
|
+
/**
|
|
166
|
+
* Update viewport height (e.g., on window resize).
|
|
167
|
+
*/
|
|
168
|
+
setViewportHeight(height: number): void;
|
|
169
|
+
/**
|
|
170
|
+
* Scroll to make a specific item visible.
|
|
171
|
+
* @param index - Item index to scroll to
|
|
172
|
+
* @param align - 'start' | 'center' | 'end' (default: 'start')
|
|
173
|
+
*/
|
|
174
|
+
scrollToItem(index: number, align?: 'start' | 'center' | 'end'): ViewportRange;
|
|
175
|
+
/**
|
|
176
|
+
* Register a callback for viewport changes.
|
|
177
|
+
*/
|
|
178
|
+
onViewport(callback: ViewportChangeCallback | null): void;
|
|
179
|
+
/**
|
|
180
|
+
* Compute the current visible range.
|
|
181
|
+
*/
|
|
182
|
+
computeViewport(): ViewportRange;
|
|
183
|
+
/**
|
|
184
|
+
* Find which item is at a given Y position.
|
|
185
|
+
* @returns Item index, or -1 if out of bounds
|
|
186
|
+
*/
|
|
187
|
+
hitTest(y: number): number;
|
|
188
|
+
/**
|
|
189
|
+
* Find the item and local Y offset within it.
|
|
190
|
+
*/
|
|
191
|
+
hitTestDetailed(y: number): {
|
|
192
|
+
index: number;
|
|
193
|
+
localY: number;
|
|
194
|
+
} | null;
|
|
195
|
+
/**
|
|
196
|
+
* Recompute all item heights (e.g., after maxWidth change).
|
|
197
|
+
* Call this after engine.updateConfig({ maxWidth: newWidth }).
|
|
198
|
+
*
|
|
199
|
+
* @returns Time taken in milliseconds
|
|
200
|
+
*/
|
|
201
|
+
relayout(): number;
|
|
202
|
+
/** Total height of all items */
|
|
203
|
+
getTotalHeight(): number;
|
|
204
|
+
/** Number of items */
|
|
205
|
+
getItemCount(): number;
|
|
206
|
+
/** Get all item heights (readonly) */
|
|
207
|
+
getHeights(): readonly number[];
|
|
208
|
+
/** Get all item offsets (readonly) */
|
|
209
|
+
getOffsets(): readonly number[];
|
|
210
|
+
/**
|
|
211
|
+
* Binary search for the item at a given Y offset.
|
|
212
|
+
* Returns the index of the item that contains the offset.
|
|
213
|
+
*/
|
|
214
|
+
private binarySearchOffset;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* @pre-markdown/layout — Cursor & Selection Engine
|
|
219
|
+
*
|
|
220
|
+
* Provides pixel-perfect cursor positioning and selection highlighting
|
|
221
|
+
* powered by pretext's zero-DOM-reflow text measurement.
|
|
222
|
+
*
|
|
223
|
+
* Key capabilities:
|
|
224
|
+
* - offsetToXY: text offset → (x, y) pixel coordinates (via pretext layout)
|
|
225
|
+
* - xyToOffset: (x, y) click → text offset (reverse hit-test)
|
|
226
|
+
* - getSelectionRects: start/end offset → array of highlight rectangles
|
|
227
|
+
* - getLineInfo: get line boundaries, count, and visual line mapping
|
|
228
|
+
*
|
|
229
|
+
* All calculations are pure arithmetic after initial prepare() —
|
|
230
|
+
* zero DOM reads, zero reflow.
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
/** A 2D point in pixel coordinates */
|
|
234
|
+
interface Point {
|
|
235
|
+
x: number;
|
|
236
|
+
y: number;
|
|
237
|
+
}
|
|
238
|
+
/** A rectangle in pixel coordinates */
|
|
239
|
+
interface Rect {
|
|
240
|
+
x: number;
|
|
241
|
+
y: number;
|
|
242
|
+
width: number;
|
|
243
|
+
height: number;
|
|
244
|
+
}
|
|
245
|
+
/** Cursor position information */
|
|
246
|
+
interface CursorPosition {
|
|
247
|
+
/** Text offset in the source string */
|
|
248
|
+
offset: number;
|
|
249
|
+
/** Visual line index (0-based, accounts for wrapping) */
|
|
250
|
+
visualLine: number;
|
|
251
|
+
/** X coordinate in pixels from left edge */
|
|
252
|
+
x: number;
|
|
253
|
+
/** Y coordinate in pixels from top */
|
|
254
|
+
y: number;
|
|
255
|
+
/** Line height in pixels */
|
|
256
|
+
lineHeight: number;
|
|
257
|
+
}
|
|
258
|
+
/** Information about a visual line (may be a wrapped sub-line) */
|
|
259
|
+
interface VisualLineInfo {
|
|
260
|
+
/** Visual line index (0-based) */
|
|
261
|
+
index: number;
|
|
262
|
+
/** Text content of this visual line */
|
|
263
|
+
text: string;
|
|
264
|
+
/** Measured width in pixels */
|
|
265
|
+
width: number;
|
|
266
|
+
/** Y offset from document top */
|
|
267
|
+
y: number;
|
|
268
|
+
/** The source (hard) line index (0-based) */
|
|
269
|
+
sourceLine: number;
|
|
270
|
+
/** Start offset in the original text */
|
|
271
|
+
startOffset: number;
|
|
272
|
+
/** End offset in the original text (exclusive) */
|
|
273
|
+
endOffset: number;
|
|
274
|
+
}
|
|
275
|
+
/** Line number mapping for display */
|
|
276
|
+
interface LineNumberInfo {
|
|
277
|
+
/** Source line number (1-based, for display) */
|
|
278
|
+
lineNumber: number;
|
|
279
|
+
/** Y position of this source line's first visual line */
|
|
280
|
+
y: number;
|
|
281
|
+
/** Number of visual lines this source line spans */
|
|
282
|
+
visualLineCount: number;
|
|
283
|
+
/** Height of all visual lines for this source line */
|
|
284
|
+
height: number;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Cursor and selection engine powered by pretext.
|
|
288
|
+
*
|
|
289
|
+
* Usage:
|
|
290
|
+
* const cursor = new CursorEngine(layoutEngine)
|
|
291
|
+
* cursor.setText(editorContent)
|
|
292
|
+
*
|
|
293
|
+
* // Click → offset
|
|
294
|
+
* const offset = cursor.xyToOffset(clickX, clickY)
|
|
295
|
+
*
|
|
296
|
+
* // Offset → position
|
|
297
|
+
* const pos = cursor.offsetToPosition(offset)
|
|
298
|
+
*
|
|
299
|
+
* // Selection rectangles
|
|
300
|
+
* const rects = cursor.getSelectionRects(selStart, selEnd)
|
|
301
|
+
*/
|
|
302
|
+
declare class CursorEngine {
|
|
303
|
+
private engine;
|
|
304
|
+
private text;
|
|
305
|
+
private hardLines;
|
|
306
|
+
private visualLines;
|
|
307
|
+
private lineNumbers;
|
|
308
|
+
private totalHeight;
|
|
309
|
+
constructor(engine: LayoutEngine);
|
|
310
|
+
/**
|
|
311
|
+
* Set the text content and compute all visual line info.
|
|
312
|
+
* Call this when the editor content changes.
|
|
313
|
+
*/
|
|
314
|
+
setText(text: string): void;
|
|
315
|
+
/** Get current text */
|
|
316
|
+
getText(): string;
|
|
317
|
+
/**
|
|
318
|
+
* Force recomputation of layout (e.g., after width change).
|
|
319
|
+
* Call after engine.updateConfig({ maxWidth: newWidth }).
|
|
320
|
+
*/
|
|
321
|
+
recompute(): void;
|
|
322
|
+
/**
|
|
323
|
+
* Convert a text offset to pixel coordinates.
|
|
324
|
+
* Returns the cursor position (x, y) for rendering a blinking cursor.
|
|
325
|
+
*/
|
|
326
|
+
offsetToPosition(offset: number): CursorPosition;
|
|
327
|
+
/**
|
|
328
|
+
* Convert pixel coordinates (from a click event) to a text offset.
|
|
329
|
+
* This is the reverse of offsetToPosition().
|
|
330
|
+
*/
|
|
331
|
+
xyToOffset(x: number, y: number): number;
|
|
332
|
+
/**
|
|
333
|
+
* Compute selection highlight rectangles for a text range.
|
|
334
|
+
* Returns one rect per visual line that intersects the selection.
|
|
335
|
+
*/
|
|
336
|
+
getSelectionRects(start: number, end: number): Rect[];
|
|
337
|
+
/** Get all visual lines */
|
|
338
|
+
getVisualLines(): readonly VisualLineInfo[];
|
|
339
|
+
/** Get visual line count (total lines including wrapped) */
|
|
340
|
+
getVisualLineCount(): number;
|
|
341
|
+
/** Get source line count (hard lines from \n) */
|
|
342
|
+
getSourceLineCount(): number;
|
|
343
|
+
/**
|
|
344
|
+
* Get line number info for rendering line numbers.
|
|
345
|
+
* Each entry represents a source line with its Y position
|
|
346
|
+
* and how many visual lines it spans (for proper alignment).
|
|
347
|
+
*/
|
|
348
|
+
getLineNumbers(): readonly LineNumberInfo[];
|
|
349
|
+
/** Get total content height in pixels */
|
|
350
|
+
getTotalHeight(): number;
|
|
351
|
+
/**
|
|
352
|
+
* Get the source line number for a given text offset.
|
|
353
|
+
* Returns 1-based line number.
|
|
354
|
+
*/
|
|
355
|
+
getLineNumberAtOffset(offset: number): number;
|
|
356
|
+
/**
|
|
357
|
+
* Get the visual line at a given Y coordinate.
|
|
358
|
+
*/
|
|
359
|
+
getVisualLineAtY(y: number): VisualLineInfo | null;
|
|
360
|
+
/**
|
|
361
|
+
* Get all visual lines for a given source line.
|
|
362
|
+
*/
|
|
363
|
+
getVisualLinesForSourceLine(sourceLine: number): VisualLineInfo[];
|
|
364
|
+
/**
|
|
365
|
+
* Find word boundaries at a given offset.
|
|
366
|
+
* Returns [start, end] offsets of the word.
|
|
367
|
+
*/
|
|
368
|
+
getWordBoundary(offset: number): [number, number];
|
|
369
|
+
/**
|
|
370
|
+
* Find the visual line containing a given text offset.
|
|
371
|
+
* Uses binary search for O(log n) performance.
|
|
372
|
+
*/
|
|
373
|
+
private findVisualLineByOffset;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* @pre-markdown/layout — Line Renderer
|
|
378
|
+
*
|
|
379
|
+
* Provides pretext-based line number rendering and soft-wrap computation.
|
|
380
|
+
* Replaces naive "one div per line" approach with accurate visual line mapping
|
|
381
|
+
* that correctly handles soft-wrapped (long) lines.
|
|
382
|
+
*
|
|
383
|
+
* Key features:
|
|
384
|
+
* - Accurate line number positioning that aligns with soft-wrapped text
|
|
385
|
+
* - Incremental updates: only recompute changed lines
|
|
386
|
+
* - Active line tracking (current cursor line highlight)
|
|
387
|
+
* - Virtual rendering for large documents (only render visible line numbers)
|
|
388
|
+
* - Pure pretext computation — zero DOM measurement needed
|
|
389
|
+
*/
|
|
390
|
+
|
|
391
|
+
/** Configuration for the line renderer */
|
|
392
|
+
interface LineRendererConfig {
|
|
393
|
+
/** The CursorEngine instance (provides visual line mapping) */
|
|
394
|
+
cursor: CursorEngine;
|
|
395
|
+
/** Container element for line numbers */
|
|
396
|
+
container: HTMLElement;
|
|
397
|
+
/** Line height in pixels (must match editor) */
|
|
398
|
+
lineHeight: number;
|
|
399
|
+
/** Class name for active line highlight */
|
|
400
|
+
activeClass?: string;
|
|
401
|
+
/** Whether to use virtual rendering (default: true for > 1000 lines) */
|
|
402
|
+
virtual?: boolean;
|
|
403
|
+
/** Extra lines to render above/below viewport in virtual mode */
|
|
404
|
+
overscan?: number;
|
|
405
|
+
}
|
|
406
|
+
/** A rendered line number entry */
|
|
407
|
+
interface RenderedLineNumber {
|
|
408
|
+
/** Source line number (1-based) */
|
|
409
|
+
lineNumber: number;
|
|
410
|
+
/** Y position in pixels */
|
|
411
|
+
y: number;
|
|
412
|
+
/** Height spanning all visual lines of this source line */
|
|
413
|
+
height: number;
|
|
414
|
+
/** Whether this is the active (cursor) line */
|
|
415
|
+
isActive: boolean;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Renders line numbers with correct alignment for soft-wrapped text.
|
|
419
|
+
* Uses pretext layout data from CursorEngine for pixel-perfect positioning.
|
|
420
|
+
*/
|
|
421
|
+
declare class LineRenderer {
|
|
422
|
+
private cursor;
|
|
423
|
+
private container;
|
|
424
|
+
private lineHeight;
|
|
425
|
+
private activeClass;
|
|
426
|
+
private virtual;
|
|
427
|
+
private overscan;
|
|
428
|
+
private activeLine;
|
|
429
|
+
private lastScrollTop;
|
|
430
|
+
private lastViewportHeight;
|
|
431
|
+
private renderedRange;
|
|
432
|
+
private pool;
|
|
433
|
+
constructor(config: LineRendererConfig);
|
|
434
|
+
/**
|
|
435
|
+
* Set the active (cursor) line. Highlighted in the gutter.
|
|
436
|
+
* @param lineNumber - 1-based source line number
|
|
437
|
+
*/
|
|
438
|
+
setActiveLine(lineNumber: number): void;
|
|
439
|
+
/** Get the current active line number (1-based) */
|
|
440
|
+
getActiveLine(): number;
|
|
441
|
+
/**
|
|
442
|
+
* Full render: rebuild all line numbers.
|
|
443
|
+
* Use for initial render or after text content changes.
|
|
444
|
+
*/
|
|
445
|
+
render(): void;
|
|
446
|
+
/**
|
|
447
|
+
* Render all line numbers (non-virtual mode).
|
|
448
|
+
* Uses absolutely-positioned divs for correct alignment with soft-wrapped text.
|
|
449
|
+
*/
|
|
450
|
+
private renderAll;
|
|
451
|
+
/**
|
|
452
|
+
* Virtual rendering: only render line numbers visible in the viewport.
|
|
453
|
+
* Uses absolute positioning with a spacer for correct scroll height.
|
|
454
|
+
*/
|
|
455
|
+
renderVirtual(): void;
|
|
456
|
+
/**
|
|
457
|
+
* Update scroll position for virtual rendering.
|
|
458
|
+
* Call from the editor's scroll event handler.
|
|
459
|
+
*/
|
|
460
|
+
updateScroll(scrollTop: number, viewportHeight: number): void;
|
|
461
|
+
/**
|
|
462
|
+
* Update after text changes. Re-renders line numbers.
|
|
463
|
+
*/
|
|
464
|
+
update(): void;
|
|
465
|
+
/**
|
|
466
|
+
* Get the number of visual lines for each source line.
|
|
467
|
+
* Useful for understanding how text wraps without DOM measurement.
|
|
468
|
+
*
|
|
469
|
+
* @returns Array where index = source line (0-based), value = visual line count
|
|
470
|
+
*/
|
|
471
|
+
getWrapInfo(): number[];
|
|
472
|
+
/**
|
|
473
|
+
* Check if a source line is soft-wrapped (spans multiple visual lines).
|
|
474
|
+
*/
|
|
475
|
+
isLineWrapped(sourceLine: number): boolean;
|
|
476
|
+
/**
|
|
477
|
+
* Get total visual line count (including wrapped lines).
|
|
478
|
+
*/
|
|
479
|
+
getTotalVisualLines(): number;
|
|
480
|
+
/**
|
|
481
|
+
* Dispose resources.
|
|
482
|
+
*/
|
|
483
|
+
dispose(): void;
|
|
484
|
+
private getPooledDiv;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* @pre-markdown/layout
|
|
489
|
+
*
|
|
490
|
+
* Pretext-based text layout engine.
|
|
491
|
+
* Handles text measurement, line breaking, and virtual viewport layout.
|
|
492
|
+
*
|
|
493
|
+
* Uses @chenglou/pretext for zero-DOM-reflow text measurement:
|
|
494
|
+
* prepare() → one-time text analysis (cached)
|
|
495
|
+
* layout() → pure-arithmetic line breaking (hot path)
|
|
496
|
+
*/
|
|
497
|
+
|
|
498
|
+
interface LayoutConfig {
|
|
499
|
+
/** CSS font string (e.g., '16px Inter'). Must be loaded before use. */
|
|
500
|
+
font: string;
|
|
501
|
+
/** Line height in pixels (must match CSS line-height) */
|
|
502
|
+
lineHeight: number;
|
|
503
|
+
/** Maximum width for text wrapping (pixels) */
|
|
504
|
+
maxWidth: number;
|
|
505
|
+
/** White-space mode: 'normal' (default) or 'pre-wrap' */
|
|
506
|
+
whiteSpace?: 'normal' | 'pre-wrap';
|
|
507
|
+
/** Viewport buffer multiplier (default 2 = 2x viewport above & below) */
|
|
508
|
+
viewportBuffer?: number;
|
|
509
|
+
/** Font for code blocks / inline code (defaults to main font) */
|
|
510
|
+
codeFont?: string;
|
|
511
|
+
/** Line height for code blocks (defaults to main lineHeight) */
|
|
512
|
+
codeLineHeight?: number;
|
|
513
|
+
}
|
|
514
|
+
interface LayoutLine {
|
|
515
|
+
/** Line text content */
|
|
516
|
+
text: string;
|
|
517
|
+
/** Measured width in pixels */
|
|
518
|
+
width: number;
|
|
519
|
+
/** Y position from top */
|
|
520
|
+
y: number;
|
|
521
|
+
/** Source line index (in document paragraphs) */
|
|
522
|
+
sourceIndex: number;
|
|
523
|
+
}
|
|
524
|
+
interface LayoutResult {
|
|
525
|
+
/** Total height of all lines */
|
|
526
|
+
height: number;
|
|
527
|
+
/** Total number of visual lines */
|
|
528
|
+
lineCount: number;
|
|
529
|
+
/** Individual line layouts (only populated when requested) */
|
|
530
|
+
lines?: LayoutLine[];
|
|
531
|
+
}
|
|
532
|
+
interface ViewportLayoutResult {
|
|
533
|
+
/** Lines visible in the viewport */
|
|
534
|
+
visibleLines: LayoutLine[];
|
|
535
|
+
/** Total height of the entire document */
|
|
536
|
+
totalHeight: number;
|
|
537
|
+
/** Y offset of the first visible line */
|
|
538
|
+
startY: number;
|
|
539
|
+
/** Index of first visible line */
|
|
540
|
+
startIndex: number;
|
|
541
|
+
/** Index of last visible line (exclusive) */
|
|
542
|
+
endIndex: number;
|
|
543
|
+
}
|
|
544
|
+
interface MeasurementBackend {
|
|
545
|
+
/** One-time text analysis (expensive, cache result) */
|
|
546
|
+
prepare(text: string, font: string, options?: PrepareOptions): PreparedText;
|
|
547
|
+
/** One-time text analysis with segment info */
|
|
548
|
+
prepareWithSegments(text: string, font: string, options?: PrepareOptions): PreparedTextWithSegments;
|
|
549
|
+
/** Pure-arithmetic layout (cheap, can call per frame) */
|
|
550
|
+
layout(prepared: PreparedText, maxWidth: number, lineHeight: number): LayoutResult$1;
|
|
551
|
+
/** Layout with per-line info */
|
|
552
|
+
layoutWithLines(prepared: PreparedTextWithSegments, maxWidth: number, lineHeight: number): LayoutLinesResult;
|
|
553
|
+
/** Clear internal caches */
|
|
554
|
+
clearCache(): void;
|
|
555
|
+
/** Set locale for text segmentation */
|
|
556
|
+
setLocale(locale?: string): void;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Fallback backend for environments without Canvas (e.g. Node.js tests).
|
|
560
|
+
* Uses character-count heuristic for approximate measurement.
|
|
561
|
+
*/
|
|
562
|
+
declare function createFallbackBackend(avgCharWidth?: number): MeasurementBackend;
|
|
563
|
+
/**
|
|
564
|
+
* High-performance text layout engine powered by @chenglou/pretext.
|
|
565
|
+
*
|
|
566
|
+
* Two-phase pipeline:
|
|
567
|
+
* 1. prepare() — one-time text analysis (~1-5ms per paragraph, cached via LRU)
|
|
568
|
+
* 2. layout() — pure arithmetic (~0.0002ms, safe for animation frames)
|
|
569
|
+
*
|
|
570
|
+
* In environments without Canvas (Node.js), set a fallback backend
|
|
571
|
+
* via the constructor or `setBackend()`.
|
|
572
|
+
*/
|
|
573
|
+
declare class LayoutEngine {
|
|
574
|
+
private config;
|
|
575
|
+
private backend;
|
|
576
|
+
private preparedCache;
|
|
577
|
+
private preparedSegCache;
|
|
578
|
+
constructor(config: LayoutConfig, backend?: MeasurementBackend);
|
|
579
|
+
/** Update layout configuration. Invalidates cache if font changes. */
|
|
580
|
+
updateConfig(config: Partial<LayoutConfig>): void;
|
|
581
|
+
/** Get current configuration (readonly). */
|
|
582
|
+
getConfig(): Readonly<LayoutConfig>;
|
|
583
|
+
/** Replace the measurement backend (e.g. for testing). */
|
|
584
|
+
setBackend(backend: MeasurementBackend): void;
|
|
585
|
+
/** Set locale for text segmentation. */
|
|
586
|
+
setLocale(locale?: string): void;
|
|
587
|
+
/**
|
|
588
|
+
* Compute height and line count for a text block.
|
|
589
|
+
* Uses pretext prepare() + layout() pipeline.
|
|
590
|
+
* The PreparedText is cached by (text, font) key.
|
|
591
|
+
*/
|
|
592
|
+
computeLayout(text: string): LayoutResult;
|
|
593
|
+
/**
|
|
594
|
+
* Compute layout for code blocks using code font.
|
|
595
|
+
* Falls back to main font if codeFont is not configured.
|
|
596
|
+
*/
|
|
597
|
+
computeCodeLayout(text: string): LayoutResult;
|
|
598
|
+
/**
|
|
599
|
+
* Compute layout with individual line information.
|
|
600
|
+
* Uses prepareWithSegments() + layoutWithLines().
|
|
601
|
+
*/
|
|
602
|
+
computeLayoutWithLines(text: string): LayoutResult;
|
|
603
|
+
/**
|
|
604
|
+
* Compute layout for only the visible viewport.
|
|
605
|
+
* Key performance optimization: only measure and position visible lines.
|
|
606
|
+
* Includes configurable buffer (default 2x viewport) for smooth scrolling.
|
|
607
|
+
*/
|
|
608
|
+
computeViewportLayout(text: string, scrollTop: number, viewportHeight: number): ViewportLayoutResult;
|
|
609
|
+
/**
|
|
610
|
+
* Compute layout for an array of text blocks (paragraphs).
|
|
611
|
+
* Returns cumulative heights for virtual scrolling.
|
|
612
|
+
*/
|
|
613
|
+
computeDocumentLayout(paragraphs: string[]): {
|
|
614
|
+
totalHeight: number;
|
|
615
|
+
paragraphOffsets: number[];
|
|
616
|
+
paragraphHeights: number[];
|
|
617
|
+
};
|
|
618
|
+
/**
|
|
619
|
+
* Find which paragraph and line is at a given scrollTop position.
|
|
620
|
+
*/
|
|
621
|
+
hitTest(paragraphs: string[], scrollTop: number): {
|
|
622
|
+
paragraphIndex: number;
|
|
623
|
+
lineIndex: number;
|
|
624
|
+
} | null;
|
|
625
|
+
private _lastParagraphs;
|
|
626
|
+
private _lastHeights;
|
|
627
|
+
private _lastTotalHeight;
|
|
628
|
+
/**
|
|
629
|
+
* Incremental document layout — reuses cached heights for unchanged paragraphs.
|
|
630
|
+
* Only recomputes layout for paragraphs that changed since the last call.
|
|
631
|
+
* Use this for real-time editing instead of computeDocumentLayout().
|
|
632
|
+
*
|
|
633
|
+
* Returns the same structure as computeDocumentLayout().
|
|
634
|
+
*/
|
|
635
|
+
updateDocumentLayout(paragraphs: string[]): {
|
|
636
|
+
totalHeight: number;
|
|
637
|
+
paragraphOffsets: number[];
|
|
638
|
+
paragraphHeights: number[];
|
|
639
|
+
changedIndices: number[];
|
|
640
|
+
};
|
|
641
|
+
/**
|
|
642
|
+
* Get the cached total height from the last updateDocumentLayout() call.
|
|
643
|
+
* O(1) — no recomputation.
|
|
644
|
+
*/
|
|
645
|
+
getCachedTotalHeight(): number;
|
|
646
|
+
/** Invalidate cache for a specific text block. */
|
|
647
|
+
invalidateCache(text?: string): void;
|
|
648
|
+
/** Clear all caches including pretext internal cache. */
|
|
649
|
+
clearAllCaches(): void;
|
|
650
|
+
/** Get current cache statistics. */
|
|
651
|
+
getCacheStats(): {
|
|
652
|
+
preparedSize: number;
|
|
653
|
+
segmentSize: number;
|
|
654
|
+
};
|
|
655
|
+
private cacheKey;
|
|
656
|
+
private getPrepared;
|
|
657
|
+
private getPreparedWithSegments;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export { CursorEngine, type CursorPosition, type LayoutConfig, LayoutEngine, type LayoutLine, type LayoutResult, type LineNumberInfo, LineRenderer, type LineRendererConfig, type MeasurementBackend, type Point, type Rect, type RenderedLineNumber, type ViewportChangeCallback, type ViewportLayoutResult, type ViewportRange, VirtualList, type VirtualListConfig, type VirtualListItem, type VisualLineInfo, type WorkerMeasurementBackend, createFallbackBackend, createWorkerBackend };
|