-
Notifications
You must be signed in to change notification settings - Fork 149
Expand file tree
/
Copy pathresolved-layout.ts
More file actions
468 lines (450 loc) · 18.2 KB
/
resolved-layout.ts
File metadata and controls
468 lines (450 loc) · 18.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
import type {
ColumnLayout,
ColumnRegion,
DrawingBlock,
FlowMode,
Fragment,
ImageBlock,
ImageFragmentMetadata,
Line,
ListBlock,
ListMeasure,
PageMargins,
ParagraphBlock,
ParagraphBorders,
ParagraphMeasure,
SectionVerticalAlign,
SourceAnchor,
TableBlock,
TableMeasure,
} from './index.js';
/** A fully resolved layout ready for the next-generation paint pipeline. */
export type ResolvedLayout = {
/** Schema version for forward compatibility. */
version: 1;
/** Rendering flow mode used to produce this layout. */
flowMode: FlowMode;
/** Gap between pages in pixels (0 when unset). */
pageGap: number;
/** Pre-computed block versions for painter-side cache invalidation. */
blockVersions?: Record<string, string>;
/** Resolved pages with normalized dimensions. */
pages: ResolvedPage[];
/** Document epoch identifier from the source layout. Used for change tracking in the painter. */
layoutEpoch?: number;
};
/** A single resolved page with stable identity and normalized dimensions. */
export type ResolvedPage = {
/** Stable page identifier (e.g. `page-0`). */
id: string;
/** 0-based page index. */
index: number;
/** 1-based page number (from Page.number). */
number: number;
/** Page width in pixels (resolved from page.size?.w ?? layout.pageSize.w). */
width: number;
/** Page height in pixels (resolved from page.size?.h ?? layout.pageSize.h). */
height: number;
/** Resolved paint items for this page. */
items: ResolvedPaintItem[];
/** Page margins from the source page. Used for ruler rendering and header/footer positioning. */
margins?: PageMargins;
/** Extra bottom space reserved for footnotes (px). Used for footer space calculation. */
footnoteReserved?: number;
/** Formatted page number text (e.g. "i", "ii" for Roman numeral sections). */
numberText?: string;
/** Vertical alignment of content within this page. */
vAlign?: SectionVerticalAlign;
/** Base section margins before header/footer inflation. Used for vAlign centering calculations. */
baseMargins?: { top: number; bottom: number };
/** 0-based index of the section this page belongs to. */
sectionIndex?: number;
/** Header/footer reference IDs for this page's section. */
sectionRefs?: {
headerRefs?: { default?: string; first?: string; even?: string; odd?: string };
footerRefs?: { default?: string; first?: string; even?: string; odd?: string };
};
/** Page orientation. */
orientation?: 'portrait' | 'landscape';
/** Column layout configuration for this page (reflects page-start config). */
columns?: ColumnLayout;
/** Vertical column regions when continuous section breaks change column layout mid-page. */
columnRegions?: ColumnRegion[];
};
/** Union of all resolved paint item kinds. */
export type ResolvedPaintItem =
| ResolvedGroupItem
| ResolvedFragmentItem
| ResolvedTableItem
| ResolvedImageItem
| ResolvedDrawingItem;
/** A group of nested resolved paint items (for future use). */
export type ResolvedGroupItem = {
kind: 'group';
id: string;
x: number;
y: number;
width: number;
height: number;
children: ResolvedPaintItem[];
};
/**
* A resolved fragment wrapper item.
* Carries positioning and metadata needed to create the fragment's DOM wrapper.
* Inner content rendering is delegated to the Fragment-based render path via
* fragmentIndex; see the compat-fallback note on that field.
*/
export type ResolvedFragmentItem = {
kind: 'fragment';
/** Stable identifier matching fragmentKey() semantics from the painter. */
id: string;
/** 0-based page index this item belongs to. */
pageIndex: number;
/** Left position in pixels. */
x: number;
/** Top position in pixels. */
y: number;
/** Width in pixels. */
width: number;
/** Height in pixels (computed from measure data for para/list-item). */
height: number;
/** Stacking order for anchored images/drawings. */
zIndex?: number;
/** Source fragment kind — used by the painter for wrapper style decisions. */
fragmentKind: Fragment['kind'];
/** Source fragment back-pointer. Lets the painter iterate resolved items
* and pass the underlying fragment to render helpers without indexing
* back into the legacy `page.fragments` array. */
fragment: Fragment;
/** Block ID. Written to data-block-id. */
blockId: string;
/**
* Index back into page.fragments.
*
* AIDEV-NOTE: compat-fallback. The painter currently reads inner content from
* Fragment via this index because ResolvedFragmentItem does not carry full
* content yet. Becomes unused once paint items are self-describing; do not
* promote this to a permanent API surface.
*/
fragmentIndex: number;
/** ProseMirror start position for click-to-position mapping. */
pmStart?: number;
/** ProseMirror end position for click-to-position mapping. */
pmEnd?: number;
/** Whether this fragment continues from a previous page. */
continuesFromPrev?: boolean;
/** Whether this fragment continues on the next page. */
continuesOnNext?: boolean;
/** List marker box width in pixels (para/list-item only). */
markerWidth?: number;
/** Pre-resolved paragraph content for non-table paragraph fragments. */
content?: ResolvedParagraphContent;
/** Pre-computed SDT container key for boundary grouping (`structuredContent:<id>` or `documentSection:<id>`). */
sdtContainerKey?: string | null;
/** Pre-computed hash of paragraph borders for between-border grouping. */
paragraphBorderHash?: string;
/** Pre-extracted paragraph borders for between-border rendering. */
paragraphBorders?: ParagraphBorders;
/** Pre-computed visual/layout signature (blockVersion + fragment-specific data). */
version?: string;
/** Pre-computed source/evidence metadata signature. Does not imply visual/layout geometry changed. */
evidenceVersion?: string;
/** Combined paint reuse signature. DomPainter uses this to refresh source-linked DOM metadata. */
paintCacheVersion?: string;
/** Pre-extracted block for paragraph (ParagraphBlock) or list-item (ListBlock) fragments. */
block?: ParagraphBlock | ListBlock;
/** Pre-extracted measure for paragraph (ParagraphMeasure) or list-item (ListMeasure) fragments. */
measure?: ParagraphMeasure | ListMeasure;
/** Optional DOCX source evidence preserved for intelligence adapters and paint snapshots. */
sourceAnchor?: SourceAnchor;
};
/** Resolved paragraph content for non-table paragraph/list-item fragments. */
export type ResolvedParagraphContent = {
/** The lines to render, with all layout data pre-resolved. */
lines: ResolvedTextLineItem[];
/** Drop cap rendering data. Only present on first fragment of a paragraph with a drop cap. */
dropCap?: ResolvedDropCapItem;
/** List marker rendering data. Only present on first fragment of a list paragraph. */
marker?: ResolvedListMarkerItem;
/** Whether this fragment continues from a previous page. */
continuesFromPrev?: boolean;
/** Whether this fragment continues on the next page. */
continuesOnNext?: boolean;
/** Whether the source paragraph ends with a lineBreak run. */
paragraphEndsWithLineBreak?: boolean;
};
/** A single resolved text line with pre-computed rendering geometry. */
export type ResolvedTextLineItem = {
/** The source Line data (segments, leaders, bars, dimensions, run indices). */
line: Line;
/** Global line index within the paragraph (fragment.fromLine + localIndex). */
lineIndex: number;
/** Pre-computed available width for justify calculations. */
availableWidth: number;
/** Whether to skip justification for this line (last line of paragraph). */
skipJustify: boolean;
/** Pre-computed CSS paddingLeft in pixels. */
paddingLeftPx: number;
/** Pre-computed CSS paddingRight in pixels. */
paddingRightPx: number;
/** Pre-computed CSS textIndent in pixels (0 when not applicable). */
textIndentPx: number;
/** Whether this is a list first line (indent handled by marker, not CSS). */
isListFirstLine: boolean;
/** Resolved text-start position for list first lines with explicit segment positioning. */
resolvedListTextStartPx?: number;
/** Whether this line has explicit segment positioning (tabs). */
hasExplicitSegmentPositioning: boolean;
/** Pre-computed indent offset for the segment positioning path in renderLine. */
indentOffset: number;
};
/** Resolved drop cap rendering data. */
export type ResolvedDropCapItem = {
/** Drop cap text content. */
text: string;
/** Drop cap mode. */
mode: 'drop' | 'margin';
/** Font family. */
fontFamily: string;
/** Font size in pixels. */
fontSize: number;
/** Bold styling. */
bold?: boolean;
/** Italic styling. */
italic?: boolean;
/** Text color. */
color?: string;
/** Vertical position offset in pixels. */
position?: number;
/** Measured width in pixels. */
width?: number;
/** Measured height in pixels. */
height?: number;
};
// ============================================================================
// Kind-specific resolved items (PR7: table, image, drawing)
// ============================================================================
/**
* A resolved table fragment with pre-extracted block/measure data.
* Replaces blockLookup.get() in the table render path.
*/
export type ResolvedTableItem = {
kind: 'fragment';
/** Discriminant for table fragments. */
fragmentKind: 'table';
/** Stable identifier matching fragmentKey() semantics from the painter. */
id: string;
/** 0-based page index this item belongs to. */
pageIndex: number;
/** Left position in pixels. */
x: number;
/** Top position in pixels. */
y: number;
/** Width in pixels. */
width: number;
/** Height in pixels (from fragment.height). */
height: number;
/** Stacking order (tables typically don't have zIndex at fragment level). */
zIndex?: number;
/** Block ID — written to data-block-id. */
blockId: string;
/**
* Index back into page.fragments.
*
* AIDEV-NOTE: compat-fallback. The painter currently reads inner content from
* Fragment via this index because ResolvedFragmentItem does not carry full
* content yet. Becomes unused once paint items are self-describing; do not
* promote this to a permanent API surface.
*/
fragmentIndex: number;
/** Source TableFragment back-pointer (see ResolvedFragmentItem.fragment). */
fragment: Fragment;
/** ProseMirror start position for click-to-position mapping. */
pmStart?: number;
/** ProseMirror end position for click-to-position mapping. */
pmEnd?: number;
/** Whether this table fragment continues from a previous page. */
continuesFromPrev?: boolean;
/** Whether this table fragment continues on the next page. */
continuesOnNext?: boolean;
/** Pre-extracted TableBlock (replaces blockLookup.get()). */
block: TableBlock;
/** Pre-extracted TableMeasure (replaces blockLookup.get()). */
measure: TableMeasure;
/** Pre-computed cell spacing: measure.cellSpacingPx ?? getCellSpacingPx(block.attrs?.cellSpacing). */
cellSpacingPx: number;
/** Pre-computed effective column widths: fragment.columnWidths ?? measure.columnWidths. */
effectiveColumnWidths: number[];
/** Pre-computed SDT container key for boundary grouping (`structuredContent:<id>` or `documentSection:<id>`). */
sdtContainerKey?: string | null;
/** Pre-computed visual/layout signature (blockVersion + fragment-specific data). */
version?: string;
/** Pre-computed source/evidence metadata signature. Does not imply visual/layout geometry changed. */
evidenceVersion?: string;
/** Combined paint reuse signature. DomPainter uses this to refresh source-linked DOM metadata. */
paintCacheVersion?: string;
/** Optional DOCX source evidence preserved for intelligence adapters and paint snapshots. */
sourceAnchor?: SourceAnchor;
};
/**
* A resolved image fragment with pre-extracted block data.
* Replaces blockLookup.get() in the image render path.
*/
export type ResolvedImageItem = {
kind: 'fragment';
/** Discriminant for image fragments. */
fragmentKind: 'image';
/** Stable identifier matching fragmentKey() semantics from the painter. */
id: string;
/** 0-based page index this item belongs to. */
pageIndex: number;
/** Left position in pixels. */
x: number;
/** Top position in pixels. */
y: number;
/** Width in pixels. */
width: number;
/** Height in pixels. */
height: number;
/** Stacking order for anchored images. */
zIndex?: number;
/** Block ID — written to data-block-id. */
blockId: string;
/**
* Index back into page.fragments.
*
* AIDEV-NOTE: compat-fallback. The painter currently reads inner content from
* Fragment via this index because ResolvedFragmentItem does not carry full
* content yet. Becomes unused once paint items are self-describing; do not
* promote this to a permanent API surface.
*/
fragmentIndex: number;
/** Source ImageFragment back-pointer (see ResolvedFragmentItem.fragment). */
fragment: Fragment;
/** ProseMirror start position for click-to-position mapping. */
pmStart?: number;
/** ProseMirror end position for click-to-position mapping. */
pmEnd?: number;
/** Pre-extracted ImageBlock (replaces blockLookup.get()). */
block: ImageBlock;
/** Image metadata for interactive resizing (original dimensions, aspect ratio). */
metadata?: ImageFragmentMetadata;
/** Pre-computed SDT container key for boundary grouping (typically null for images). */
sdtContainerKey?: string | null;
/** Pre-computed visual/layout signature (blockVersion + fragment-specific data). */
version?: string;
/** Pre-computed source/evidence metadata signature. Does not imply visual/layout geometry changed. */
evidenceVersion?: string;
/** Combined paint reuse signature. DomPainter uses this to refresh source-linked DOM metadata. */
paintCacheVersion?: string;
/** Optional DOCX source evidence preserved for intelligence adapters and paint snapshots. */
sourceAnchor?: SourceAnchor;
};
/**
* A resolved drawing fragment with pre-extracted block data.
* Replaces blockLookup.get() in the drawing render path.
*/
export type ResolvedDrawingItem = {
kind: 'fragment';
/** Discriminant for drawing fragments. */
fragmentKind: 'drawing';
/** Stable identifier matching fragmentKey() semantics from the painter. */
id: string;
/** 0-based page index this item belongs to. */
pageIndex: number;
/** Left position in pixels. */
x: number;
/** Top position in pixels. */
y: number;
/** Width in pixels. */
width: number;
/** Height in pixels. */
height: number;
/** Stacking order for anchored drawings. */
zIndex?: number;
/** Block ID — written to data-block-id. */
blockId: string;
/**
* Index back into page.fragments.
*
* AIDEV-NOTE: compat-fallback. The painter currently reads inner content from
* Fragment via this index because ResolvedFragmentItem does not carry full
* content yet. Becomes unused once paint items are self-describing; do not
* promote this to a permanent API surface.
*/
fragmentIndex: number;
/** Source DrawingFragment back-pointer (see ResolvedFragmentItem.fragment). */
fragment: Fragment;
/** ProseMirror start position for click-to-position mapping. */
pmStart?: number;
/** ProseMirror end position for click-to-position mapping. */
pmEnd?: number;
/** Pre-extracted DrawingBlock (replaces blockLookup.get()). */
block: DrawingBlock;
/** Pre-computed SDT container key for boundary grouping (typically null for drawings). */
sdtContainerKey?: string | null;
/** Pre-computed visual/layout signature (blockVersion + fragment-specific data). */
version?: string;
/** Pre-computed source/evidence metadata signature. Does not imply visual/layout geometry changed. */
evidenceVersion?: string;
/** Combined paint reuse signature. DomPainter uses this to refresh source-linked DOM metadata. */
paintCacheVersion?: string;
/** Optional DOCX source evidence preserved for intelligence adapters and paint snapshots. */
sourceAnchor?: SourceAnchor;
};
/** Type guard: checks whether a resolved paint item is a ResolvedTableItem. */
export function isResolvedTableItem(item: ResolvedPaintItem): item is ResolvedTableItem {
return item.kind === 'fragment' && 'fragmentKind' in item && item.fragmentKind === 'table' && 'measure' in item;
}
/** Type guard: checks whether a resolved paint item is a ResolvedImageItem. */
export function isResolvedImageItem(item: ResolvedPaintItem): item is ResolvedImageItem {
return item.kind === 'fragment' && 'fragmentKind' in item && item.fragmentKind === 'image' && 'block' in item;
}
/** Type guard: checks whether a resolved paint item is a ResolvedDrawingItem. */
export function isResolvedDrawingItem(item: ResolvedPaintItem): item is ResolvedDrawingItem {
return item.kind === 'fragment' && 'fragmentKind' in item && item.fragmentKind === 'drawing' && 'block' in item;
}
/** A resolved header/footer page — mirrors HeaderFooterPage but with resolved items. */
export type ResolvedHeaderFooterPage = {
number: number;
numberText?: string;
items: ResolvedPaintItem[];
};
/** A resolved header/footer layout — mirrors HeaderFooterLayout but with resolved pages. */
export type ResolvedHeaderFooterLayout = {
height: number;
minY?: number;
maxY?: number;
renderHeight?: number;
pages: ResolvedHeaderFooterPage[];
};
/** Resolved list marker rendering data with pre-computed positioning. */
export type ResolvedListMarkerItem = {
/** Marker text content (e.g., "1.", "a)", bullet). */
text: string;
/** Horizontal justification. */
justification: 'left' | 'right' | 'center';
/** Suffix type after marker. */
suffix: 'tab' | 'space' | 'nothing';
/** Whether marker should be hidden (vanish property). */
vanish?: boolean;
/** Pre-computed left position of marker container in pixels. */
markerStartPx: number;
/** Pre-computed tab/space suffix width in pixels. */
suffixWidthPx: number;
/** CSS paddingLeft for the first line when marker is present. */
firstLinePaddingLeftPx: number;
/** Extra padding adjustment for center-justified markers. */
centerPaddingAdjustPx?: number;
/** Marker run styling. */
run: {
fontFamily: string;
fontSize: number;
bold?: boolean;
italic?: boolean;
color?: string;
letterSpacing?: number;
};
/** Optional DOCX source evidence for list-marker observations. */
sourceAnchor?: SourceAnchor;
};