Skip to content

Commit 54bb36a

Browse files
authored
refactor(layout): move paragraph rendering into layout-resolved (#2614)
1 parent ea9df3e commit 54bb36a

11 files changed

Lines changed: 1781 additions & 427 deletions

File tree

packages/layout-engine/contracts/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,6 +1971,10 @@ export type {
19711971
ResolvedPaintItem,
19721972
ResolvedGroupItem,
19731973
ResolvedFragmentItem,
1974+
ResolvedParagraphContent,
1975+
ResolvedTextLineItem,
1976+
ResolvedDropCapItem,
1977+
ResolvedListMarkerItem,
19741978
} from './resolved-layout.js';
19751979

19761980
export * as Engines from './engines/index.js';

packages/layout-engine/contracts/src/resolved-layout.ts

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FlowMode, Fragment } from './index.js';
1+
import type { FlowMode, Fragment, Line } from './index.js';
22

33
/** A fully resolved layout ready for the next-generation paint pipeline. */
44
export type ResolvedLayout = {
@@ -69,4 +69,101 @@ export type ResolvedFragmentItem = {
6969
blockId: string;
7070
/** Index within page.fragments — bridge to legacy content rendering. */
7171
fragmentIndex: number;
72+
/** Pre-resolved paragraph content for non-table paragraph fragments. */
73+
content?: ResolvedParagraphContent;
74+
};
75+
76+
/** Resolved paragraph content for non-table paragraph/list-item fragments. */
77+
export type ResolvedParagraphContent = {
78+
/** The lines to render, with all layout data pre-resolved. */
79+
lines: ResolvedTextLineItem[];
80+
/** Drop cap rendering data. Only present on first fragment of a paragraph with a drop cap. */
81+
dropCap?: ResolvedDropCapItem;
82+
/** List marker rendering data. Only present on first fragment of a list paragraph. */
83+
marker?: ResolvedListMarkerItem;
84+
/** Whether this fragment continues from a previous page. */
85+
continuesFromPrev?: boolean;
86+
/** Whether this fragment continues on the next page. */
87+
continuesOnNext?: boolean;
88+
/** Whether the source paragraph ends with a lineBreak run. */
89+
paragraphEndsWithLineBreak?: boolean;
90+
};
91+
92+
/** A single resolved text line with pre-computed rendering geometry. */
93+
export type ResolvedTextLineItem = {
94+
/** The source Line data (segments, leaders, bars, dimensions, run indices). */
95+
line: Line;
96+
/** Global line index within the paragraph (fragment.fromLine + localIndex). */
97+
lineIndex: number;
98+
/** Pre-computed available width for justify calculations. */
99+
availableWidth: number;
100+
/** Whether to skip justification for this line (last line of paragraph). */
101+
skipJustify: boolean;
102+
/** Pre-computed CSS paddingLeft in pixels. */
103+
paddingLeftPx: number;
104+
/** Pre-computed CSS paddingRight in pixels. */
105+
paddingRightPx: number;
106+
/** Pre-computed CSS textIndent in pixels (0 when not applicable). */
107+
textIndentPx: number;
108+
/** Whether this is a list first line (indent handled by marker, not CSS). */
109+
isListFirstLine: boolean;
110+
/** Resolved text-start position for list first lines with explicit segment positioning. */
111+
resolvedListTextStartPx?: number;
112+
/** Whether this line has explicit segment positioning (tabs). */
113+
hasExplicitSegmentPositioning: boolean;
114+
/** Pre-computed indent offset for the segment positioning path in renderLine. */
115+
indentOffset: number;
116+
};
117+
118+
/** Resolved drop cap rendering data. */
119+
export type ResolvedDropCapItem = {
120+
/** Drop cap text content. */
121+
text: string;
122+
/** Drop cap mode. */
123+
mode: 'drop' | 'margin';
124+
/** Font family. */
125+
fontFamily: string;
126+
/** Font size in pixels. */
127+
fontSize: number;
128+
/** Bold styling. */
129+
bold?: boolean;
130+
/** Italic styling. */
131+
italic?: boolean;
132+
/** Text color. */
133+
color?: string;
134+
/** Vertical position offset in pixels. */
135+
position?: number;
136+
/** Measured width in pixels. */
137+
width?: number;
138+
/** Measured height in pixels. */
139+
height?: number;
140+
};
141+
142+
/** Resolved list marker rendering data with pre-computed positioning. */
143+
export type ResolvedListMarkerItem = {
144+
/** Marker text content (e.g., "1.", "a)", bullet). */
145+
text: string;
146+
/** Horizontal justification. */
147+
justification: 'left' | 'right' | 'center';
148+
/** Suffix type after marker. */
149+
suffix: 'tab' | 'space' | 'nothing';
150+
/** Whether marker should be hidden (vanish property). */
151+
vanish?: boolean;
152+
/** Pre-computed left position of marker container in pixels. */
153+
markerStartPx: number;
154+
/** Pre-computed tab/space suffix width in pixels. */
155+
suffixWidthPx: number;
156+
/** CSS paddingLeft for the first line when marker is present. */
157+
firstLinePaddingLeftPx: number;
158+
/** Extra padding adjustment for center-justified markers. */
159+
centerPaddingAdjustPx?: number;
160+
/** Marker run styling. */
161+
run: {
162+
fontFamily: string;
163+
fontSize: number;
164+
bold?: boolean;
165+
italic?: boolean;
166+
color?: string;
167+
letterSpacing?: number;
168+
};
72169
};

packages/layout-engine/layout-resolved/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"test": "vitest run"
2020
},
2121
"dependencies": {
22+
"@superdoc/common": "workspace:*",
2223
"@superdoc/contracts": "workspace:*"
2324
},
2425
"devDependencies": {

0 commit comments

Comments
 (0)