@stll/folio-core 0.49.0 → 0.50.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.
Files changed (91) hide show
  1. package/dist/ai-edits/headless.d.ts +37 -1
  2. package/dist/ai-edits/headless.js +47 -16
  3. package/dist/display-list/dom/renderDisplayListToDom.d.ts +10 -1
  4. package/dist/display-list/dom/renderDisplayListToDom.js +8 -7
  5. package/dist/display-list/html/renderDisplayListToHtml.d.ts +21 -0
  6. package/dist/display-list/html/renderDisplayListToHtml.js +126 -0
  7. package/dist/display-list/selectDisplayPages.d.ts +6 -0
  8. package/dist/display-list/selectDisplayPages.js +44 -0
  9. package/dist/export-pdf.d.ts +35 -2
  10. package/dist/export-pdf.js +51 -20
  11. package/dist/fonts/fontsourceFaces.d.ts +47 -0
  12. package/dist/fonts/fontsourceFaces.js +230 -0
  13. package/dist/layout-bridge/convert/flowBlockPostprocessing.d.ts +8 -0
  14. package/dist/layout-bridge/convert/flowBlockPostprocessing.js +106 -0
  15. package/dist/layout-bridge/convert/flowBorders.d.ts +17 -0
  16. package/dist/layout-bridge/convert/flowBorders.js +31 -0
  17. package/dist/layout-bridge/convert/flowConversionShared.d.ts +111 -0
  18. package/dist/layout-bridge/convert/flowConversionShared.js +23 -0
  19. package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -1
  20. package/dist/layout-bridge/convert/footnoteLayout.js +2 -1
  21. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +2 -1
  22. package/dist/layout-bridge/convert/imageConversion.d.ts +33 -0
  23. package/dist/layout-bridge/convert/imageConversion.js +118 -0
  24. package/dist/layout-bridge/convert/listMarkers.d.ts +28 -0
  25. package/dist/layout-bridge/convert/listMarkers.js +98 -0
  26. package/dist/layout-bridge/convert/noteReferences.d.ts +14 -0
  27. package/dist/layout-bridge/convert/noteReferences.js +29 -0
  28. package/dist/layout-bridge/convert/pageBreakSplitting.d.ts +16 -0
  29. package/dist/layout-bridge/convert/pageBreakSplitting.js +140 -0
  30. package/dist/layout-bridge/convert/paragraphAttrs.d.ts +20 -0
  31. package/dist/layout-bridge/convert/paragraphAttrs.js +234 -0
  32. package/dist/layout-bridge/convert/paragraphConversion.d.ts +12 -0
  33. package/dist/layout-bridge/convert/paragraphConversion.js +112 -0
  34. package/dist/layout-bridge/convert/paragraphRuns.d.ts +15 -0
  35. package/dist/layout-bridge/convert/paragraphRuns.js +281 -0
  36. package/dist/layout-bridge/convert/runFormattingMerge.d.ts +19 -0
  37. package/dist/layout-bridge/convert/runFormattingMerge.js +167 -0
  38. package/dist/layout-bridge/convert/runMarkFormatting.d.ts +11 -0
  39. package/dist/layout-bridge/convert/runMarkFormatting.js +291 -0
  40. package/dist/layout-bridge/convert/sectionBoundaries.d.ts +35 -0
  41. package/dist/layout-bridge/convert/sectionBoundaries.js +114 -0
  42. package/dist/layout-bridge/convert/tableConversion.d.ts +12 -0
  43. package/dist/layout-bridge/convert/tableConversion.js +303 -0
  44. package/dist/layout-bridge/convert/textBoxFill.d.ts +20 -0
  45. package/dist/layout-bridge/convert/textBoxFill.js +42 -0
  46. package/dist/layout-bridge/convert/textFormattingConversion.d.ts +22 -0
  47. package/dist/layout-bridge/convert/textFormattingConversion.js +120 -0
  48. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +5 -113
  49. package/dist/layout-bridge/convert/toFlowBlocks.js +11 -2082
  50. package/dist/layout-bridge/engine/hitTest.js +1 -1
  51. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  52. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  53. package/dist/layout-bridge/engine/selectionRects.js +1 -1
  54. package/dist/layout-engine/imageLayout.d.ts +9 -0
  55. package/dist/layout-engine/imageLayout.js +48 -0
  56. package/dist/layout-engine/index.d.ts +3 -18
  57. package/dist/layout-engine/index.js +12 -937
  58. package/dist/layout-engine/layoutFlowShared.d.ts +6 -0
  59. package/dist/layout-engine/layoutFlowShared.js +8 -0
  60. package/dist/layout-engine/measure/crossRunWords.d.ts +45 -0
  61. package/dist/layout-engine/measure/crossRunWords.js +172 -0
  62. package/dist/layout-engine/measure/floatingLineClearance.d.ts +24 -0
  63. package/dist/layout-engine/measure/floatingLineClearance.js +44 -0
  64. package/dist/layout-engine/measure/inlineObjectMetrics.d.ts +6 -0
  65. package/dist/layout-engine/measure/inlineObjectMetrics.js +52 -0
  66. package/dist/layout-engine/measure/justification.d.ts +32 -0
  67. package/dist/layout-engine/measure/justification.js +83 -0
  68. package/dist/layout-engine/measure/lineFitting.d.ts +48 -0
  69. package/dist/layout-engine/measure/lineFitting.js +145 -0
  70. package/dist/layout-engine/measure/lineTypography.d.ts +44 -0
  71. package/dist/layout-engine/measure/lineTypography.js +124 -0
  72. package/dist/layout-engine/measure/measureBlocks.js +2 -1
  73. package/dist/layout-engine/measure/measureParagraph.d.ts +1 -20
  74. package/dist/layout-engine/measure/measureParagraph.js +32 -734
  75. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +109 -0
  76. package/dist/layout-engine/measure/paragraphMeasureShared.js +82 -0
  77. package/dist/layout-engine/measure/paragraphTabs.d.ts +18 -0
  78. package/dist/layout-engine/measure/paragraphTabs.js +78 -0
  79. package/dist/layout-engine/paragraphLayout.d.ts +23 -0
  80. package/dist/layout-engine/paragraphLayout.js +204 -0
  81. package/dist/layout-engine/sectionLayout.d.ts +45 -0
  82. package/dist/layout-engine/sectionLayout.js +216 -0
  83. package/dist/layout-engine/tableLayout.d.ts +17 -0
  84. package/dist/layout-engine/tableLayout.js +369 -0
  85. package/dist/layout-engine/textBoxLayout.d.ts +15 -0
  86. package/dist/layout-engine/textBoxLayout.js +137 -0
  87. package/dist/layout-painter/renderPage.js +1 -0
  88. package/dist/paged-layout/sectionBlockWidths.d.ts +2 -1
  89. package/dist/paged-layout/sectionBlockWidths.js +1 -1
  90. package/dist/server.d.ts +3 -2
  91. package/package.json +2 -2
@@ -0,0 +1,234 @@
1
+ import { bulletMarkerFontName, convertBulletToUnicode } from "../../docx/bulletMarkers.js";
2
+ import { getFontAlternate } from "../../fonts/fontAlternates.js";
3
+ import { autospacingMatchesBase } from "../../prosemirror/autospacingBase.js";
4
+ import { advanceVisibleListMarker } from "../../prosemirror/listMarker.js";
5
+ import { directionIsRtl, directionToBidi } from "../../prosemirror/paragraphDirection.js";
6
+ import { mergeParagraphTabStops } from "../../utils/paragraphFormattingMerge.js";
7
+ import { AUTO_PARAGRAPH_SPACING_PX } from "../../utils/units.js";
8
+ import { convertBorderSpecToLayout } from "./flowBorders.js";
9
+ import { twipsToPixels } from "./flowConversionShared.js";
10
+ import { applyDeletedListMarkerAttrs, applyMarkerAllCaps, isAddedNumberingChange, isChangedNumberingChange, isRemovedNumberingChange, toListMarkerRevision } from "./listMarkers.js";
11
+ import { listMarkerFormattingFor, resolveWesternThemeFont } from "./textFormattingConversion.js";
12
+ import { paragraphNumberingLevel, paragraphNumberingReferenceId } from "@stll/docx-core/model";
13
+ //#region src/layout-bridge/convert/paragraphAttrs.ts
14
+ /**
15
+ * Converts ProseMirror paragraph attributes to layout ParagraphAttrs:
16
+ * alignment, spacing, indentation, borders, tab stops, and list markers.
17
+ */
18
+ /**
19
+ * Every `ST_Jc` member, and the flow alignment that paints it.
20
+ *
21
+ * `start` and `end` stay unresolved here: they name an edge of the writing
22
+ * direction, not a side of the page, so which of `left` and `right` paints
23
+ * them depends on the paragraph's direction. `numTab` has no flow alignment at
24
+ * all — it aligns the paragraph to the list number's tab stop, which the flow
25
+ * engine does not model — so it falls back to the start edge, which is where a
26
+ * numbered paragraph without one sits.
27
+ */
28
+ const FLOW_ALIGNMENT_BY_PARAGRAPH_ALIGNMENT = {
29
+ start: "start",
30
+ end: "end",
31
+ numTab: "start",
32
+ left: "left",
33
+ center: "center",
34
+ right: "right",
35
+ both: "justify",
36
+ distribute: "justify",
37
+ mediumKashida: "justify",
38
+ highKashida: "justify",
39
+ lowKashida: "justify",
40
+ thaiDistribute: "justify"
41
+ };
42
+ /** Resolve a logical alignment against the direction the paragraph runs in. */
43
+ const resolveFlowAlignment = (alignment, rightToLeft) => {
44
+ const flow = FLOW_ALIGNMENT_BY_PARAGRAPH_ALIGNMENT[alignment];
45
+ switch (flow) {
46
+ case "start": return rightToLeft ? "right" : "left";
47
+ case "end": return rightToLeft ? "left" : "right";
48
+ default: return flow;
49
+ }
50
+ };
51
+ function convertParagraphAttrs(pmAttrs, { theme, fontAlternates, listCounterStreams, defaultTabStopTwips, paragraphMarkFormatting }) {
52
+ const attrs = {};
53
+ if (pmAttrs.alignment) attrs.alignment = resolveFlowAlignment(pmAttrs.alignment, directionIsRtl(pmAttrs.direction));
54
+ if (pmAttrs.outlineLevel !== void 0) attrs.outlineLevel = pmAttrs.outlineLevel;
55
+ const spaceBefore = pmAttrs.spaceBefore;
56
+ const spaceAfter = pmAttrs.spaceAfter;
57
+ const lineSpacing = pmAttrs.lineSpacing;
58
+ if (typeof pmAttrs.snapToGrid === "boolean") attrs.snapToGrid = pmAttrs.snapToGrid;
59
+ const autoBefore = autospacingMatchesBase(pmAttrs._autospacingBase, "before", spaceBefore);
60
+ const autoAfter = autospacingMatchesBase(pmAttrs._autospacingBase, "after", spaceAfter);
61
+ if (autoBefore || autoAfter || typeof spaceBefore === "number" || typeof spaceAfter === "number" || typeof lineSpacing === "number") {
62
+ attrs.spacing = {};
63
+ if (autoBefore) attrs.spacing.before = AUTO_PARAGRAPH_SPACING_PX;
64
+ else if (typeof spaceBefore === "number") attrs.spacing.before = twipsToPixels(spaceBefore);
65
+ if (autoAfter) attrs.spacing.after = AUTO_PARAGRAPH_SPACING_PX;
66
+ else if (typeof spaceAfter === "number") attrs.spacing.after = twipsToPixels(spaceAfter);
67
+ if (autoBefore || autoAfter) attrs.automaticSpacing = {
68
+ ...autoBefore ? { before: true } : {},
69
+ ...autoAfter ? { after: true } : {}
70
+ };
71
+ const pmSpacingExplicit = pmAttrs.spacingExplicit;
72
+ const spacingFromDocDefaults = pmAttrs.spacingFromDocDefaults;
73
+ const spacingFromImplicitDefaultStyle = pmAttrs.spacingFromImplicitDefaultStyle;
74
+ const explicit = {};
75
+ if (autoBefore || pmSpacingExplicit?.before || spacingFromDocDefaults?.before || spacingFromImplicitDefaultStyle?.before) explicit.before = true;
76
+ if (autoAfter || pmSpacingExplicit?.after || spacingFromDocDefaults?.after || spacingFromImplicitDefaultStyle?.after) explicit.after = true;
77
+ if (explicit.before !== void 0 || explicit.after !== void 0) attrs.spacingExplicit = explicit;
78
+ if (typeof lineSpacing === "number") if (pmAttrs.lineSpacingRule === "exact" || pmAttrs.lineSpacingRule === "atLeast") {
79
+ attrs.spacing.line = twipsToPixels(lineSpacing);
80
+ attrs.spacing.lineUnit = "px";
81
+ attrs.spacing.lineRule = pmAttrs.lineSpacingRule;
82
+ } else {
83
+ attrs.spacing.line = lineSpacing / 240;
84
+ attrs.spacing.lineUnit = "multiplier";
85
+ attrs.spacing.lineRule = "auto";
86
+ }
87
+ }
88
+ let indentLeft = typeof pmAttrs.indentLeft === "number" ? pmAttrs.indentLeft : void 0;
89
+ let indentFirstLine = typeof pmAttrs.indentFirstLine === "number" ? pmAttrs.indentFirstLine : void 0;
90
+ let hangingIndent = pmAttrs.hangingIndent;
91
+ if (paragraphNumberingReferenceId(pmAttrs.numPr) !== void 0 && indentLeft === void 0 && indentFirstLine === void 0) {
92
+ indentLeft = ((paragraphNumberingLevel(pmAttrs.numPr) ?? 0) + 1) * 720;
93
+ if (indentFirstLine === void 0) {
94
+ indentFirstLine = -360;
95
+ hangingIndent = true;
96
+ }
97
+ }
98
+ if (indentLeft !== void 0 || typeof pmAttrs.indentRight === "number" || indentFirstLine !== void 0) {
99
+ attrs.indent = {};
100
+ if (indentLeft !== void 0) attrs.indent.left = twipsToPixels(indentLeft);
101
+ if (typeof pmAttrs.indentRight === "number") attrs.indent.right = twipsToPixels(pmAttrs.indentRight);
102
+ if (indentFirstLine !== void 0) if (hangingIndent) attrs.indent.hanging = Math.abs(twipsToPixels(indentFirstLine));
103
+ else attrs.indent.firstLine = twipsToPixels(indentFirstLine);
104
+ }
105
+ if (pmAttrs.styleId) attrs.styleId = pmAttrs.styleId;
106
+ if (pmAttrs.borders) {
107
+ const borders = pmAttrs.borders;
108
+ attrs.borders = {};
109
+ const convertBorder = (border) => border ? convertBorderSpecToLayout(border, theme) : void 0;
110
+ const topBorder = borders.top ? convertBorder(borders.top) : void 0;
111
+ if (topBorder) attrs.borders.top = topBorder;
112
+ const bottomBorder = borders.bottom ? convertBorder(borders.bottom) : void 0;
113
+ if (bottomBorder) attrs.borders.bottom = bottomBorder;
114
+ const leftBorder = borders.left ? convertBorder(borders.left) : void 0;
115
+ if (leftBorder) attrs.borders.left = leftBorder;
116
+ const rightBorder = borders.right ? convertBorder(borders.right) : void 0;
117
+ if (rightBorder) attrs.borders.right = rightBorder;
118
+ const betweenBorder = borders.between ? convertBorder(borders.between) : void 0;
119
+ if (betweenBorder) attrs.borders.between = betweenBorder;
120
+ const barBorder = borders.bar ? convertBorder(borders.bar) : void 0;
121
+ if (barBorder) attrs.borders.bar = barBorder;
122
+ if (!attrs.borders.top && !attrs.borders.bottom && !attrs.borders.left && !attrs.borders.right && !attrs.borders.between && !attrs.borders.bar) delete attrs.borders;
123
+ }
124
+ const shadingRgb = pmAttrs.shading?.fill?.rgb?.toUpperCase();
125
+ if (shadingRgb && shadingRgb !== "FFFFFF" && shadingRgb !== "FFFFFE") attrs.shading = `#${pmAttrs.shading?.fill?.rgb}`;
126
+ const tabs = mergeParagraphTabStops(pmAttrs.listLevelTabs ?? void 0, pmAttrs.tabs ?? void 0);
127
+ if (tabs && tabs.length > 0) {
128
+ const rightToLeft = directionIsRtl(pmAttrs.direction);
129
+ attrs.tabs = tabs.map((tab) => {
130
+ const tabStop = {
131
+ val: resolveTabAlignment(tab.alignment, rightToLeft),
132
+ pos: tab.position
133
+ };
134
+ if (tab.leader) tabStop.leader = tab.leader;
135
+ return tabStop;
136
+ });
137
+ }
138
+ if (pmAttrs.pageBreakBefore) attrs.pageBreakBefore = true;
139
+ if (pmAttrs.kinsoku !== void 0 && pmAttrs.kinsoku !== null) attrs.kinsoku = pmAttrs.kinsoku;
140
+ if (pmAttrs.overflowPunctuation !== void 0 && pmAttrs.overflowPunctuation !== null) attrs.overflowPunctuation = pmAttrs.overflowPunctuation;
141
+ if (pmAttrs.suppressAutoHyphens !== void 0 && pmAttrs.suppressAutoHyphens !== null) attrs.suppressAutoHyphens = pmAttrs.suppressAutoHyphens;
142
+ if (pmAttrs.renderedPageBreakBefore) attrs.renderedPageBreakBefore = true;
143
+ if (pmAttrs.keepNext) attrs.keepNext = true;
144
+ if (pmAttrs.keepLines) attrs.keepLines = true;
145
+ if (pmAttrs.widowControl === false) attrs.widowControl = false;
146
+ if (pmAttrs.contextualSpacing) attrs.contextualSpacing = true;
147
+ if (pmAttrs.runInWithNext) attrs.runInWithNext = true;
148
+ const bidi = directionToBidi(pmAttrs.direction);
149
+ if (bidi !== void 0) attrs.bidi = bidi;
150
+ if (pmAttrs.styleId) attrs.styleId = pmAttrs.styleId;
151
+ const propertyChanges = pmAttrs._propertyChanges ?? [];
152
+ let changedNumberingChange;
153
+ if (pmAttrs.numPr) {
154
+ attrs.numPr = pmAttrs.numPr;
155
+ if (pmAttrs.pPrMark?.kind === "del") attrs.listMarkerRevision = toListMarkerRevision("del", pmAttrs.pPrMark.info);
156
+ else if (pmAttrs.pPrMark?.kind === "ins") attrs.listMarkerRevision = toListMarkerRevision("ins", pmAttrs.pPrMark.info);
157
+ else {
158
+ const numberingAddedChange = propertyChanges.find(isAddedNumberingChange);
159
+ const currentNumPr = pmAttrs.numPr;
160
+ changedNumberingChange = propertyChanges.find((change) => isChangedNumberingChange(currentNumPr, change));
161
+ const numberingInsertionChange = numberingAddedChange ?? changedNumberingChange;
162
+ if (numberingInsertionChange) attrs.listMarkerRevision = toListMarkerRevision("ins", numberingInsertionChange.info);
163
+ }
164
+ }
165
+ const resolvedMarker = applyMarkerAllCaps(advanceVisibleListMarker(pmAttrs, listCounterStreams).marker, pmAttrs.listMarkerAllCaps);
166
+ if (resolvedMarker !== null) attrs.listMarker = resolvedMarker;
167
+ else if (pmAttrs.listMarker) attrs.listMarker = pmAttrs.listIsBullet ? convertBulletToUnicode(pmAttrs.listMarker, bulletMarkerFontName(pmAttrs.listMarkerFormatting)) : pmAttrs.listMarker;
168
+ if (pmAttrs.listIsBullet !== void 0) attrs.listIsBullet = pmAttrs.listIsBullet;
169
+ if (pmAttrs.listMarkerHidden) attrs.listMarkerHidden = true;
170
+ if (attrs.listMarker !== void 0 || pmAttrs.listMarkerFormatting) {
171
+ const listMarkerFormatting = listMarkerFormattingFor(pmAttrs.listMarkerFormatting, paragraphMarkFormatting(), theme, fontAlternates);
172
+ if (listMarkerFormatting) attrs.listMarkerFormatting = listMarkerFormatting;
173
+ }
174
+ if (pmAttrs.listMarkerAlignment) attrs.listMarkerAlignment = pmAttrs.listMarkerAlignment;
175
+ if (pmAttrs.listMarkerSuffix) attrs.listMarkerSuffix = pmAttrs.listMarkerSuffix;
176
+ if (pmAttrs.listMarkerSecondSlotOffsetTwips !== void 0) attrs.listMarkerSecondSlotOffsetTwips = pmAttrs.listMarkerSecondSlotOffsetTwips;
177
+ if (!pmAttrs.numPr) {
178
+ const numberingRemovedChange = propertyChanges.find(isRemovedNumberingChange);
179
+ if (numberingRemovedChange) {
180
+ applyDeletedListMarkerAttrs(attrs, numberingRemovedChange, void 0, theme, fontAlternates, paragraphMarkFormatting);
181
+ if (resolvedMarker !== null) {
182
+ attrs.listMarker = resolvedMarker;
183
+ attrs.listMarkerRevision = toListMarkerRevision("del", numberingRemovedChange.info);
184
+ }
185
+ }
186
+ }
187
+ if (defaultTabStopTwips !== void 0) attrs.defaultTabStopTwips = defaultTabStopTwips;
188
+ const dtf = pmAttrs.defaultTextFormatting;
189
+ if (dtf) {
190
+ if (dtf.fontSize !== void 0) attrs.defaultFontSize = dtf.fontSize / 2;
191
+ if (attrs.listMarker !== void 0 && !attrs.listMarkerHidden && (attrs.bidi === true || dtf.rtl === true || dtf.cs === true) && dtf.fontSizeCs !== void 0 && (dtf.fontSize === void 0 || dtf.fontSizeCs > dtf.fontSize)) attrs.listParagraphMarkFontSize = dtf.fontSizeCs / 2;
192
+ if (dtf.fontFamily) {
193
+ const resolvedFamily = resolveWesternThemeFont(dtf.fontFamily, theme);
194
+ if (resolvedFamily) {
195
+ attrs.defaultFontFamily = resolvedFamily;
196
+ const alternate = getFontAlternate(resolvedFamily, fontAlternates);
197
+ if (alternate) attrs.defaultAlternateFontFamily = alternate;
198
+ }
199
+ }
200
+ }
201
+ return attrs;
202
+ }
203
+ /**
204
+ * Every `ST_TabJc` member, and the layout engine's name for it.
205
+ *
206
+ * The engine's `start` and `end` are its names for the left and right edges of
207
+ * the line, not direction-aware ones, so `w:tab w:val="left"` maps to `start`
208
+ * unconditionally. `ST_TabJc`'s own `start` and `end` *are* direction-aware
209
+ * and stay unresolved here, under names the engine has no member for.
210
+ * `num`, the tab a numbered paragraph's text hangs from, has no engine
211
+ * alignment of its own and falls back to the line's start edge.
212
+ */
213
+ const TAB_ALIGNMENT_BY_TAB_STOP_ALIGNMENT = {
214
+ start: "logicalStart",
215
+ end: "logicalEnd",
216
+ num: "logicalStart",
217
+ left: "start",
218
+ right: "end",
219
+ center: "center",
220
+ decimal: "decimal",
221
+ bar: "bar",
222
+ clear: "clear"
223
+ };
224
+ /** Resolve a logical tab alignment against the direction the paragraph runs in. */
225
+ const resolveTabAlignment = (alignment, rightToLeft) => {
226
+ const mapped = TAB_ALIGNMENT_BY_TAB_STOP_ALIGNMENT[alignment];
227
+ switch (mapped) {
228
+ case "logicalStart": return rightToLeft ? "end" : "start";
229
+ case "logicalEnd": return rightToLeft ? "start" : "end";
230
+ default: return mapped;
231
+ }
232
+ };
233
+ //#endregion
234
+ export { convertParagraphAttrs };
@@ -0,0 +1,12 @@
1
+ import { ParagraphAttrs } from "../../prosemirror/schema/nodes.js";
2
+ import { ParagraphAttrs as ParagraphAttrs$1, ParagraphBlock } from "../../layout-engine/types.js";
3
+ import { FlowConversionOptions } from "./flowConversionShared.js";
4
+ import { PageBreakRunProjection } from "./paragraphRuns.js";
5
+ import { Node } from "prosemirror-model";
6
+ //#region src/layout-bridge/convert/paragraphConversion.d.ts
7
+ declare function hasVisibleParagraphPayload(attrs: ParagraphAttrs$1): boolean;
8
+ /** Whether any `w:pBdr` side draws a rule (`none`/`nil` sides draw nothing). */
9
+ declare function drawsParagraphBorder(borders: ParagraphAttrs["borders"]): boolean;
10
+ declare function convertParagraph(node: Node, startPos: number, options: FlowConversionOptions, pageBreaks?: PageBreakRunProjection[]): ParagraphBlock;
11
+ //#endregion
12
+ export { convertParagraph, drawsParagraphBorder, hasVisibleParagraphPayload };
@@ -0,0 +1,112 @@
1
+ import { getFontAlternate } from "../../fonts/fontAlternates.js";
2
+ import { setParagraphFrame } from "../../layout-engine/paragraphFrame.js";
3
+ import { expectParagraphAttrs } from "../../prosemirror/attrs/index.js";
4
+ import { expectTextBoxAnchorAttrs } from "../../prosemirror/textBoxAnchorAttrs.js";
5
+ import { nextBlockId, twipsToPixels } from "./flowConversionShared.js";
6
+ import { convertParagraphAttrs } from "./paragraphAttrs.js";
7
+ import { resolveParagraphMarkFormatting, resolveParagraphMarkOwnFormatting } from "./paragraphMarkFormatting.js";
8
+ import { paragraphToRuns } from "./paragraphRuns.js";
9
+ import { resolveWesternThemeFont } from "./textFormattingConversion.js";
10
+ import { panic } from "better-result";
11
+ import { statesNoBorder } from "@stll/docx-core/model";
12
+ //#region src/layout-bridge/convert/paragraphConversion.ts
13
+ const DETACHED_WATERMARK_HOST_ATTR = "_detachedWatermarkHost";
14
+ const expectDetachedWatermarkHostAttr = (attrs) => {
15
+ const value = Reflect.get(attrs, DETACHED_WATERMARK_HOST_ATTR);
16
+ if (value === null || value === void 0) return false;
17
+ if (typeof value !== "boolean") panic("Invalid ProseMirror detached watermark host attrs:\nparagraph.attrs._detachedWatermarkHost: Expected a boolean.");
18
+ return value;
19
+ };
20
+ /**
21
+ * Convert a paragraph node to a ParagraphBlock.
22
+ */
23
+ function hasOnlyVisuallyEmptyTextRuns(runs) {
24
+ return runs.length > 0 && runs.every((run) => run.kind === "text" && run.text.replace(/\u00a0/gu, " ").trim().length === 0);
25
+ }
26
+ function hasOnlyHiddenTextRuns(runs) {
27
+ return runs.length > 0 && runs.every((run) => run.kind === "text" && run.hidden === true);
28
+ }
29
+ function hasVisibleParagraphPayload(attrs) {
30
+ return attrs.listMarker !== void 0 && !attrs.listMarkerHidden || attrs.borders?.top !== void 0 || attrs.borders?.bottom !== void 0 || attrs.borders?.left !== void 0 || attrs.borders?.right !== void 0 || attrs.borders?.between !== void 0 || attrs.borders?.bar !== void 0 || attrs.shading !== void 0;
31
+ }
32
+ /** Whether any `w:pBdr` side draws a rule (`none`/`nil` sides draw nothing). */
33
+ function drawsParagraphBorder(borders) {
34
+ if (!borders) return false;
35
+ return [
36
+ borders.top,
37
+ borders.bottom,
38
+ borders.left,
39
+ borders.right,
40
+ borders.between,
41
+ borders.bar
42
+ ].some((border) => border?.style !== void 0 && !statesNoBorder(border.style));
43
+ }
44
+ function convertParagraph(node, startPos, options, pageBreaks) {
45
+ const pmAttrs = expectParagraphAttrs(node);
46
+ const runs = paragraphToRuns(node, startPos, options, pageBreaks);
47
+ const attrs = convertParagraphAttrs(pmAttrs, {
48
+ theme: options.theme,
49
+ fontAlternates: options.fontAlternates,
50
+ listCounterStreams: options.listCounterStreams,
51
+ defaultTabStopTwips: options.defaultTabStopTwips,
52
+ paragraphMarkFormatting: () => resolveParagraphMarkFormatting(pmAttrs, options.styleResolver)
53
+ });
54
+ if (options.numberingTabIgnoresIndent && attrs.listMarker !== void 0) attrs.listNumberingTabIgnoresIndent = true;
55
+ if (options.defaultFont !== void 0) attrs.defaultFontFamily ??= options.defaultFont;
56
+ if (options.defaultSize !== void 0) attrs.defaultFontSize ??= options.defaultSize;
57
+ if (options.lineBreakRules) attrs.lineBreakRules = options.lineBreakRules;
58
+ if (options.justificationCompatibility) attrs.justificationCompatibility = options.justificationCompatibility;
59
+ if (options.automaticHyphenation) attrs.automaticHyphenation = options.automaticHyphenation;
60
+ const defaultTextFormatting = pmAttrs.defaultTextFormatting;
61
+ if (runs.length === 0 || hasOnlyVisuallyEmptyTextRuns(runs)) {
62
+ if (pmAttrs._originalFormatting && Object.entries(pmAttrs._originalFormatting).some(([key, value]) => key !== "runProperties" && value !== void 0 && value !== null)) attrs.hasDirectParagraphFormatting = true;
63
+ const directParagraphMarkFormatting = pmAttrs._originalFormatting?.runProperties;
64
+ if (directParagraphMarkFormatting && Object.values(directParagraphMarkFormatting).some((value) => value !== void 0 && value !== null)) attrs.hasDirectParagraphMarkFormatting = true;
65
+ const paragraphMarkFormatting = resolveParagraphMarkOwnFormatting(directParagraphMarkFormatting, options.styleResolver);
66
+ if (paragraphMarkFormatting?.fontSize !== void 0) attrs.defaultFontSize = paragraphMarkFormatting.fontSize / 2;
67
+ const paragraphMarkFontFamily = paragraphMarkFormatting?.fontFamily ? resolveWesternThemeFont(paragraphMarkFormatting.fontFamily, options.theme) : void 0;
68
+ if (paragraphMarkFontFamily) {
69
+ attrs.defaultFontFamily = paragraphMarkFontFamily;
70
+ const alternate = getFontAlternate(paragraphMarkFontFamily, options.fontAlternates);
71
+ if (alternate) attrs.defaultAlternateFontFamily = alternate;
72
+ }
73
+ }
74
+ const isFullyHiddenParagraph = defaultTextFormatting?.hidden === true && (runs.length === 0 || hasOnlyHiddenTextRuns(runs));
75
+ if (isFullyHiddenParagraph && attrs.listMarker !== void 0) attrs.listMarkerHidden = true;
76
+ if (isFullyHiddenParagraph) attrs.suppressEmptyParagraphHeight = true;
77
+ if (runs.length === 0 && expectDetachedWatermarkHostAttr(node.attrs)) attrs.suppressEmptyParagraphHeight = false;
78
+ if (runs.length === 0 && pmAttrs._pageBreakCarrier === true && !hasVisibleParagraphPayload(attrs)) attrs.suppressEmptyParagraphHeight = true;
79
+ const bookmarkNames = pmAttrs.bookmarks?.map((b) => b.name);
80
+ const block = {
81
+ kind: "paragraph",
82
+ id: nextBlockId(),
83
+ runs,
84
+ attrs,
85
+ ...pmAttrs.paraId ? { paraId: pmAttrs.paraId } : {},
86
+ ...bookmarkNames && bookmarkNames.length > 0 ? { bookmarks: bookmarkNames } : {},
87
+ pmStart: startPos,
88
+ pmEnd: startPos + node.nodeSize
89
+ };
90
+ const frame = pmAttrs._originalFormatting?.frame;
91
+ if (frame !== void 0 && frame.dropCap !== "drop" && frame.dropCap !== "margin") setParagraphFrame(block, {
92
+ ...frame.width !== void 0 ? { width: twipsToPixels(frame.width) } : {},
93
+ ...frame.height !== void 0 ? { height: twipsToPixels(frame.height) } : {},
94
+ ...frame.hSpace !== void 0 ? { hSpace: twipsToPixels(frame.hSpace) } : {},
95
+ ...frame.vSpace !== void 0 ? { vSpace: twipsToPixels(frame.vSpace) } : {},
96
+ ...frame.hAnchor !== void 0 ? { hAnchor: frame.hAnchor } : {},
97
+ ...frame.vAnchor !== void 0 ? { vAnchor: frame.vAnchor } : {},
98
+ ...frame.x !== void 0 ? { x: twipsToPixels(frame.x) } : {},
99
+ ...frame.y !== void 0 ? { y: twipsToPixels(frame.y) } : {},
100
+ ...frame.xAlign !== void 0 ? { xAlign: frame.xAlign } : {},
101
+ ...frame.yAlign !== void 0 ? { yAlign: frame.yAlign } : {},
102
+ ...frame.wrap !== void 0 ? { wrap: frame.wrap } : {}
103
+ });
104
+ node.descendants((child) => {
105
+ if (child.type.name !== "textBoxAnchor") return true;
106
+ options.textBoxAnchorBlockIds.set(expectTextBoxAnchorAttrs(child).anchorId, block.id);
107
+ return false;
108
+ });
109
+ return block;
110
+ }
111
+ //#endregion
112
+ export { convertParagraph, drawsParagraphBorder, hasVisibleParagraphPayload };
@@ -0,0 +1,15 @@
1
+ import { Run, RunFormatting } from "../../layout-engine/types.js";
2
+ import { FlowConversionOptions } from "./flowConversionShared.js";
3
+ import { Node } from "prosemirror-model";
4
+ //#region src/layout-bridge/convert/paragraphRuns.d.ts
5
+ /**
6
+ * Convert a paragraph node to runs.
7
+ */
8
+ type PageBreakRunProjection = {
9
+ pmStart: number;
10
+ pmEnd: number;
11
+ trackedChange?: Pick<RunFormatting, "isInsertion" | "changeAuthor" | "changeDate" | "changeRevisionId" | "isSuggestion" | "suggestionId">;
12
+ };
13
+ declare function paragraphToRuns(node: Node, startPos: number, _options: FlowConversionOptions, pageBreaks?: PageBreakRunProjection[]): Run[];
14
+ //#endregion
15
+ export { PageBreakRunProjection, paragraphToRuns };
@@ -0,0 +1,281 @@
1
+ import { getFontAlternate } from "../../fonts/fontAlternates.js";
2
+ import { expectFieldAttrs, expectImageAttrs, expectMathAttrs, expectParagraphAttrs, expectPreservedXmlAttrs, expectSymbolAttrs } from "../../prosemirror/attrs/index.js";
3
+ import { decodeOoxmlSymbolCharacter } from "../../utils/ooxmlSymbol.js";
4
+ import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
5
+ import { buildImageRun, constrainImageToPage, hasPaintableImageSource, hasRelationshipBackedImageBox } from "./imageConversion.js";
6
+ import { isNoteReferenceMarkXml } from "./noteReferences.js";
7
+ import { applyCharacterStyleToggleFormatting, markDefaultBlackTextColorSource, mergeRunFormatting } from "./runFormattingMerge.js";
8
+ import { extractRunFormatting } from "./runMarkFormatting.js";
9
+ import { paragraphRunDefaults } from "./textFormattingConversion.js";
10
+ //#region src/layout-bridge/convert/paragraphRuns.ts
11
+ /**
12
+ * In TOC paragraphs, strip the resolved Hyperlink character-style colour and
13
+ * underline so the painter's link fallback doesn't fire. The PM doc keeps the
14
+ * original marks so copy/paste out of a TOC still carries the Hyperlink
15
+ * styling like Word does. Applies to both text and field runs — a TOC entry's
16
+ * page number is a PAGEREF field inside the entry's hyperlink.
17
+ *
18
+ * Mutates `formatting` in place; cheaper than re-cloning per run.
19
+ */
20
+ function stripTocHyperlinkStyle(formatting) {
21
+ if (!formatting.hyperlink) return;
22
+ formatting.hyperlink.noDefaultStyle = true;
23
+ delete formatting.color;
24
+ delete formatting.underline;
25
+ }
26
+ const isTrackedRunMark = ({ type }) => type.name === "insertion" || type.name === "deletion";
27
+ /**
28
+ * Inline wrappers own marks that apply to every projected descendant. A
29
+ * descendant's mark of the same type is more specific, and any descendant
30
+ * revision replaces (rather than combines with) an inherited revision.
31
+ */
32
+ const mergeProjectedInlineMarks = (inheritedMarks, ownMarks) => {
33
+ if (inheritedMarks.length === 0) return ownMarks;
34
+ const ownMarkTypes = new Set(ownMarks.map(({ type }) => type));
35
+ const hasOwnTrackedMark = ownMarks.some(isTrackedRunMark);
36
+ return [...inheritedMarks.filter((mark) => !ownMarkTypes.has(mark.type) && !(hasOwnTrackedMark && isTrackedRunMark(mark))), ...ownMarks];
37
+ };
38
+ function paragraphToRuns(node, startPos, _options, pageBreaks) {
39
+ const runs = [];
40
+ const offset = startPos + 1;
41
+ const theme = _options.theme;
42
+ const fontAlternates = _options.fontAlternates;
43
+ const pmAttrs = expectParagraphAttrs(node);
44
+ const paraDefaults = paragraphRunDefaults(pmAttrs, theme, fontAlternates);
45
+ const paragraphStyleId = pmAttrs.styleId;
46
+ const inTocParagraph = pmAttrs._tableOfContentsLevel !== void 0 || tableOfContentsStyleLevel({ styleId: paragraphStyleId }) !== void 0;
47
+ let leadingRenderedPageBreakPending = pmAttrs.renderedPageBreakBefore === true;
48
+ function pushRunsForChild(child, childPos, inheritedMarks = []) {
49
+ if (child.type.name === "bookmarkBoundary") return;
50
+ if (child.type.name === "renderedPageBreak") {
51
+ if (leadingRenderedPageBreakPending) {
52
+ leadingRenderedPageBreakPending = false;
53
+ return;
54
+ }
55
+ runs.push({
56
+ kind: "renderedPageBreak",
57
+ pmStart: childPos,
58
+ pmEnd: childPos + child.nodeSize
59
+ });
60
+ return;
61
+ }
62
+ const effectiveMarks = mergeProjectedInlineMarks(inheritedMarks, child.marks);
63
+ if (child.type.name === "pageBreakRun") {
64
+ const trackedChange = extractRunFormatting(effectiveMarks, theme, fontAlternates);
65
+ if (trackedChange.isDeletion) {
66
+ runs.push({
67
+ kind: "text",
68
+ text: "",
69
+ isDeletion: true,
70
+ ...trackedChange.changeAuthor !== void 0 ? { changeAuthor: trackedChange.changeAuthor } : {},
71
+ ...trackedChange.changeDate !== void 0 ? { changeDate: trackedChange.changeDate } : {},
72
+ ...trackedChange.changeRevisionId !== void 0 ? { changeRevisionId: trackedChange.changeRevisionId } : {},
73
+ ...trackedChange.isSuggestion === true ? { isSuggestion: true } : {},
74
+ ...trackedChange.suggestionId !== void 0 ? { suggestionId: trackedChange.suggestionId } : {},
75
+ pmStart: childPos,
76
+ pmEnd: childPos + child.nodeSize
77
+ });
78
+ return;
79
+ }
80
+ pageBreaks?.push({
81
+ pmStart: childPos,
82
+ pmEnd: childPos + child.nodeSize,
83
+ ...trackedChange.isInsertion ? { trackedChange: {
84
+ isInsertion: true,
85
+ ...trackedChange.changeAuthor !== void 0 ? { changeAuthor: trackedChange.changeAuthor } : {},
86
+ ...trackedChange.changeDate !== void 0 ? { changeDate: trackedChange.changeDate } : {},
87
+ ...trackedChange.changeRevisionId !== void 0 ? { changeRevisionId: trackedChange.changeRevisionId } : {},
88
+ ...trackedChange.isSuggestion === true ? { isSuggestion: true } : {},
89
+ ...trackedChange.suggestionId !== void 0 ? { suggestionId: trackedChange.suggestionId } : {}
90
+ } } : {}
91
+ });
92
+ return;
93
+ }
94
+ if (child.type.name !== "sdt") leadingRenderedPageBreakPending = false;
95
+ if (child.isText && child.text) {
96
+ const formatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
97
+ applyCharacterStyleToggleFormatting({
98
+ formatting,
99
+ marks: effectiveMarks,
100
+ paragraphFormatting: pmAttrs.defaultTextFormatting,
101
+ styleResolver: _options.styleResolver
102
+ });
103
+ if (inTocParagraph) stripTocHyperlinkStyle(formatting);
104
+ const run = {
105
+ kind: "text",
106
+ text: child.text,
107
+ ...mergeRunFormatting(paraDefaults, formatting),
108
+ pmStart: childPos,
109
+ pmEnd: childPos + child.nodeSize
110
+ };
111
+ runs.push(run);
112
+ return;
113
+ }
114
+ if (child.type.name === "symbol") {
115
+ const attrs = expectSymbolAttrs(child);
116
+ const text = decodeOoxmlSymbolCharacter(attrs.char);
117
+ if (text === null) return;
118
+ const formatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
119
+ applyCharacterStyleToggleFormatting({
120
+ formatting,
121
+ marks: effectiveMarks,
122
+ paragraphFormatting: pmAttrs.defaultTextFormatting,
123
+ styleResolver: _options.styleResolver
124
+ });
125
+ if (inTocParagraph) stripTocHyperlinkStyle(formatting);
126
+ const alternateFontFamily = getFontAlternate(attrs.font, fontAlternates);
127
+ formatting.fontFamily = attrs.font;
128
+ if (alternateFontFamily) formatting.alternateFontFamily = alternateFontFamily;
129
+ runs.push({
130
+ kind: "text",
131
+ text,
132
+ ...mergeRunFormatting(paraDefaults, formatting),
133
+ pmStart: childPos,
134
+ pmEnd: childPos + child.nodeSize
135
+ });
136
+ return;
137
+ }
138
+ if (child.type.name === "preservedXml") {
139
+ const { xml, text: capturedText } = expectPreservedXmlAttrs(child);
140
+ const text = capturedText === "" && _options.noteReferenceMarkText !== void 0 && isNoteReferenceMarkXml(xml) ? _options.noteReferenceMarkText : capturedText;
141
+ if (text === "") return;
142
+ const formatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
143
+ applyCharacterStyleToggleFormatting({
144
+ formatting,
145
+ marks: effectiveMarks,
146
+ paragraphFormatting: pmAttrs.defaultTextFormatting,
147
+ styleResolver: _options.styleResolver
148
+ });
149
+ runs.push({
150
+ kind: "text",
151
+ text,
152
+ ...mergeRunFormatting(paraDefaults, formatting),
153
+ pmStart: childPos,
154
+ pmEnd: childPos + child.nodeSize
155
+ });
156
+ return;
157
+ }
158
+ if (child.type.name === "hardBreak") {
159
+ runs.push({
160
+ kind: "lineBreak",
161
+ pmStart: childPos,
162
+ pmEnd: childPos + child.nodeSize
163
+ });
164
+ return;
165
+ }
166
+ if (child.type.name === "tab") {
167
+ const formatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
168
+ applyCharacterStyleToggleFormatting({
169
+ formatting,
170
+ marks: effectiveMarks,
171
+ paragraphFormatting: pmAttrs.defaultTextFormatting,
172
+ styleResolver: _options.styleResolver
173
+ });
174
+ const run = {
175
+ kind: "tab",
176
+ ...mergeRunFormatting(paraDefaults, formatting),
177
+ pmStart: childPos,
178
+ pmEnd: childPos + child.nodeSize
179
+ };
180
+ runs.push(run);
181
+ return;
182
+ }
183
+ if (child.type.name === "image") {
184
+ const attrs = expectImageAttrs(child);
185
+ if (!hasPaintableImageSource(attrs) && !hasRelationshipBackedImageBox(attrs)) return;
186
+ const constrained = constrainImageToPage(attrs.width ?? 100, attrs.height ?? 100, _options.pageContentHeight);
187
+ const trackedFmt = extractRunFormatting(effectiveMarks, theme, fontAlternates);
188
+ const run = buildImageRun(attrs, constrained, childPos, childPos + child.nodeSize, trackedFmt, _options.forceAnchorLayoutInCell);
189
+ runs.push(run);
190
+ return;
191
+ }
192
+ if (child.type.name === "structuredField") {
193
+ let containsPageBreak = false;
194
+ child.descendants((descendant) => {
195
+ if (descendant.type.name === "pageBreakRun") {
196
+ containsPageBreak = true;
197
+ return false;
198
+ }
199
+ return true;
200
+ });
201
+ if (containsPageBreak) {
202
+ const fieldContentStart = childPos + 1;
203
+ child.forEach((fieldChild, fieldChildOffset) => {
204
+ pushRunsForChild(fieldChild, fieldContentStart + fieldChildOffset, effectiveMarks);
205
+ });
206
+ return;
207
+ }
208
+ }
209
+ if (child.type.name === "field" || child.type.name === "structuredField") {
210
+ const attrs = expectFieldAttrs(child);
211
+ const ft = attrs.fieldType;
212
+ let mappedType = "OTHER";
213
+ if (ft === "PAGE") mappedType = "PAGE";
214
+ else if (ft === "NUMPAGES") mappedType = "NUMPAGES";
215
+ else if (ft === "DATE") mappedType = "DATE";
216
+ else if (ft === "TIME") mappedType = "TIME";
217
+ const extractedFieldFormatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
218
+ applyCharacterStyleToggleFormatting({
219
+ formatting: extractedFieldFormatting,
220
+ marks: effectiveMarks,
221
+ paragraphFormatting: pmAttrs.defaultTextFormatting,
222
+ styleResolver: _options.styleResolver
223
+ });
224
+ if (inTocParagraph) stripTocHyperlinkStyle(extractedFieldFormatting);
225
+ const fieldFormatting = markDefaultBlackTextColorSource(extractedFieldFormatting, paraDefaults);
226
+ const run = {
227
+ kind: "field",
228
+ fieldType: mappedType,
229
+ instruction: attrs.instruction,
230
+ fallback: _options.numberedRefResults?.get(child) ?? attrs.displayText ?? "",
231
+ ...attrs.fldLock ? { fldLock: true } : {},
232
+ pmStart: childPos,
233
+ pmEnd: childPos + child.nodeSize,
234
+ ...fieldFormatting
235
+ };
236
+ runs.push(run);
237
+ return;
238
+ }
239
+ if (child.type.name === "math") {
240
+ const attrs = expectMathAttrs(child);
241
+ const plainText = attrs.plainText || "[equation]";
242
+ runs.push({
243
+ kind: "math",
244
+ display: attrs.display ?? "inline",
245
+ ommlXml: attrs.ommlXml,
246
+ plainText,
247
+ italic: true,
248
+ fontFamily: "Cambria Math",
249
+ pmStart: childPos,
250
+ pmEnd: childPos + child.nodeSize
251
+ });
252
+ return;
253
+ }
254
+ if (child.type.name === "sdt") {
255
+ const sdtInnerOffset = childPos + 1;
256
+ child.forEach((sdtChild, sdtChildOffset) => {
257
+ pushRunsForChild(sdtChild, sdtInnerOffset + sdtChildOffset, effectiveMarks);
258
+ });
259
+ }
260
+ }
261
+ node.forEach((child, childOffset) => {
262
+ pushRunsForChild(child, offset + childOffset);
263
+ });
264
+ const { defaultFont, defaultSize } = _options;
265
+ if (defaultFont !== void 0 || defaultSize !== void 0) for (const run of runs) switch (run.kind) {
266
+ case "text":
267
+ case "tab":
268
+ case "renderedPageBreak":
269
+ case "field":
270
+ case "math":
271
+ if (defaultFont !== void 0) run.fontFamily ??= defaultFont;
272
+ if (defaultSize !== void 0) run.fontSize ??= defaultSize;
273
+ break;
274
+ case "image":
275
+ case "lineBreak": break;
276
+ default:
277
+ }
278
+ return runs;
279
+ }
280
+ //#endregion
281
+ export { paragraphToRuns };