@stll/folio-core 0.47.6 → 0.48.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 (163) hide show
  1. package/dist/ai-edits/word-diff.d.ts +25 -6
  2. package/dist/ai-edits/word-diff.js +210 -38
  3. package/dist/controller/fontReadiness.js +3 -3
  4. package/dist/controller/hyphenationReadiness.d.ts +19 -0
  5. package/dist/controller/hyphenationReadiness.js +47 -0
  6. package/dist/controller/layoutPipeline.d.ts +3 -0
  7. package/dist/controller/layoutPipeline.js +59 -17
  8. package/dist/display-list/build/buildDisplayList.js +39 -12
  9. package/dist/display-list/build/pageFurniture.d.ts +7 -1
  10. package/dist/display-list/build/pageFurniture.js +21 -8
  11. package/dist/display-list/build/paragraphPrimitives.js +5 -4
  12. package/dist/display-list/build/tablePrimitives.js +10 -6
  13. package/dist/docx/attributeRemainder.js +10 -2
  14. package/dist/docx/blockContentParser.d.ts +2 -0
  15. package/dist/docx/blockContentParser.js +77 -62
  16. package/dist/docx/commentParser.d.ts +2 -1
  17. package/dist/docx/commentParser.js +57 -45
  18. package/dist/docx/containerChildren.d.ts +21 -8
  19. package/dist/docx/containerChildren.js +39 -9
  20. package/dist/docx/documentParser.d.ts +5 -1
  21. package/dist/docx/documentParser.js +8 -5
  22. package/dist/docx/documentSectionFacts.d.ts +22 -0
  23. package/dist/docx/documentSectionFacts.js +43 -0
  24. package/dist/docx/fieldParser.d.ts +8 -1
  25. package/dist/docx/fieldParser.js +47 -3
  26. package/dist/docx/fontTableParser.js +34 -27
  27. package/dist/docx/footnoteParser.d.ts +3 -2
  28. package/dist/docx/footnoteParser.js +15 -12
  29. package/dist/docx/groupDrawingParser.d.ts +2 -1
  30. package/dist/docx/groupDrawingParser.js +3 -6
  31. package/dist/docx/headerFooterParser.d.ts +7 -2
  32. package/dist/docx/headerFooterParser.js +15 -5
  33. package/dist/docx/hyperlinkParser.d.ts +48 -36
  34. package/dist/docx/hyperlinkParser.js +97 -82
  35. package/dist/docx/numberingParser.js +108 -84
  36. package/dist/docx/paragraphParser.d.ts +5 -1
  37. package/dist/docx/paragraphParser.js +381 -362
  38. package/dist/docx/paragraphProperties.js +123 -109
  39. package/dist/docx/paragraphPropertySource.d.ts +6 -1
  40. package/dist/docx/paragraphPropertySource.js +9 -1
  41. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  42. package/dist/docx/paragraphTextBoxEnrichment.js +17 -6
  43. package/dist/docx/parser.d.ts +7 -1
  44. package/dist/docx/parser.js +33 -16
  45. package/dist/docx/previewBudget.d.ts +55 -33
  46. package/dist/docx/previewBudget.js +59 -65
  47. package/dist/docx/relsParser.js +9 -1
  48. package/dist/docx/rezip.js +41 -58
  49. package/dist/docx/runParser.d.ts +4 -1
  50. package/dist/docx/runParser.js +172 -184
  51. package/dist/docx/sdtProperties.js +87 -84
  52. package/dist/docx/sectionParser.js +169 -160
  53. package/dist/docx/serializer/paragraphSerializer.js +42 -38
  54. package/dist/docx/serializer/settingsSerializer.js +19 -1
  55. package/dist/docx/settingsParser.js +8 -0
  56. package/dist/docx/streamingXmlParser.d.ts +6 -2
  57. package/dist/docx/streamingXmlParser.js +12 -7
  58. package/dist/docx/styleParser.js +34 -8
  59. package/dist/docx/tableParser.d.ts +21 -12
  60. package/dist/docx/tableParser.js +518 -441
  61. package/dist/docx/textBoxParser.d.ts +8 -3
  62. package/dist/docx/textBoxParser.js +3 -3
  63. package/dist/docx/verbatimCapture.d.ts +10 -8
  64. package/dist/docx/verbatimCapture.js +45 -2
  65. package/dist/docx/vmlImageParser.d.ts +15 -13
  66. package/dist/docx/vmlImageParser.js +75 -60
  67. package/dist/docx/vmlPreview.d.ts +3 -3
  68. package/dist/docx/vmlPreview.js +3 -4
  69. package/dist/docx/xmlNamespaceContext.d.ts +9 -0
  70. package/dist/docx/xmlNamespaceContext.js +54 -0
  71. package/dist/docx/xmlParser.d.ts +6 -3
  72. package/dist/docx/xmlParser.js +11 -41
  73. package/dist/docx/xmlResourceLimits.d.ts +2 -1
  74. package/dist/docx/xmlResourceLimits.js +4 -1
  75. package/dist/fonts/headlessMeasure.js +0 -0
  76. package/dist/headless-layout.d.ts +11 -2
  77. package/dist/headless-layout.js +153 -107
  78. package/dist/internal/paragraphFormattingSerialization.js +3 -3
  79. package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
  80. package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
  81. package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
  82. package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
  83. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
  84. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
  85. package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
  86. package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
  87. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
  88. package/dist/layout-bridge/convert/toFlowBlocks.js +258 -35
  89. package/dist/layout-bridge/engine/hitTest.js +5 -3
  90. package/dist/layout-bridge/engine/selectionRects.js +5 -3
  91. package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
  92. package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
  93. package/dist/layout-engine/index.d.ts +2 -2
  94. package/dist/layout-engine/index.js +70 -11
  95. package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
  96. package/dist/layout-engine/justificationCompatibility.js +9 -5
  97. package/dist/layout-engine/keep-together.d.ts +11 -5
  98. package/dist/layout-engine/keep-together.js +39 -16
  99. package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
  100. package/dist/layout-engine/layoutInstrumentation.js +8 -1
  101. package/dist/layout-engine/measure/advanceComposition.js +4 -7
  102. package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
  103. package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
  104. package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
  105. package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
  106. package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
  107. package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
  108. package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
  109. package/dist/layout-engine/measure/lineBreaks.js +10 -3
  110. package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
  111. package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
  112. package/dist/layout-engine/measure/measureBlocks.js +42 -8
  113. package/dist/layout-engine/measure/measureContainer.js +5 -5
  114. package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
  115. package/dist/layout-engine/measure/measureHelpers.js +21 -5
  116. package/dist/layout-engine/measure/measureParagraph.js +109 -30
  117. package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
  118. package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
  119. package/dist/layout-engine/measure/tabCalculator.js +2 -1
  120. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  121. package/dist/layout-engine/measure/tableCellFloating.js +51 -19
  122. package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
  123. package/dist/layout-engine/measure/tableCellGrid.js +45 -5
  124. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
  125. package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
  126. package/dist/layout-engine/noteAreaFlow.js +83 -0
  127. package/dist/layout-engine/paginator.d.ts +10 -1
  128. package/dist/layout-engine/paginator.js +13 -1
  129. package/dist/layout-engine/tableRowBreak.js +3 -1
  130. package/dist/layout-engine/types.d.ts +89 -3
  131. package/dist/layout-engine/types.js +96 -1
  132. package/dist/layout-painter/index.d.ts +3 -0
  133. package/dist/layout-painter/renderPage.js +32 -83
  134. package/dist/layout-painter/renderParagraph.d.ts +6 -1
  135. package/dist/layout-painter/renderParagraph.js +34 -22
  136. package/dist/layout-painter/renderTable.js +26 -13
  137. package/dist/prosemirror/attrs/index.d.ts +9 -9
  138. package/dist/prosemirror/attrs/index.js +43 -11
  139. package/dist/prosemirror/conversion/fromProseDoc.js +8 -1
  140. package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
  141. package/dist/prosemirror/conversion/markInterner.js +90 -0
  142. package/dist/prosemirror/conversion/toProseDoc.js +77 -41
  143. package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
  144. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  145. package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
  146. package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
  147. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +2 -1
  148. package/dist/prosemirror/listMarker.d.ts +1 -0
  149. package/dist/prosemirror/listMarker.js +2 -0
  150. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  151. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  152. package/dist/prosemirror/schema/nodes.d.ts +13 -1
  153. package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
  154. package/dist/prosemirror/textBoxHostParagraph.js +21 -0
  155. package/dist/prosemirror/validation.js +91 -80
  156. package/dist/utils/createDocument.js +10 -1
  157. package/dist/utils/scriptSegments.d.ts +33 -4
  158. package/dist/utils/scriptSegments.js +39 -6
  159. package/dist/utils/textFormattingMerge.d.ts +17 -3
  160. package/dist/utils/textFormattingMerge.js +18 -12
  161. package/dist/utils/trailingText.d.ts +22 -0
  162. package/dist/utils/trailingText.js +32 -0
  163. package/package.json +2 -2
@@ -0,0 +1,227 @@
1
+ import { calculateColumnWidths } from "../../layout-engine/paginator.js";
2
+ import { resolveEffectiveParagraphSpacingTree } from "../../layout-engine/paragraphSpacing.js";
3
+ import { stripFlowBlockPmAnchors } from "../../layout-engine/types.js";
4
+ import { getMargins, getPageSize } from "../../paged-layout/sectionGeometry.js";
5
+ import { getColumns } from "../sectionColumns.js";
6
+ import { collectEndnoteRefs, convertFootnoteToContent, convertNoteStoryToFlowBlocks } from "./footnoteLayout.js";
7
+ import { panic } from "better-result";
8
+ //#region src/layout-bridge/convert/endnoteLayout.ts
9
+ /**
10
+ * Endnote areas (ECMA-376 §17.11).
11
+ *
12
+ * Endnotes are not reserved per page the way footnotes are. `w:pos` puts them
13
+ * after the last body block of the document (`docEnd`, the default) or of each
14
+ * section (`sectEnd`), where they paginate as body content does: they are
15
+ * spliced into the body's block list and the same measure and paginate path
16
+ * places them. Each area opens with the `w:separator` endnote story, and every
17
+ * page it continues onto opens with the `w:continuationSeparator` story. The
18
+ * parsed model keeps only normal notes, so both stories are the ones a package
19
+ * is written with: one paragraph, no space after, single line spacing, holding
20
+ * the separator mark.
21
+ *
22
+ * A note's blocks carry positions in the note's own document, so they are
23
+ * stripped of them before joining the body flow: a click, a caret or a
24
+ * selection in the body must never resolve into a note. The painted note is
25
+ * addressed by its story instead, which is what the note editor opens.
26
+ */
27
+ /**
28
+ * The separator story a notes part is written with (folio writes it too):
29
+ * `<w:p><w:pPr><w:spacing w:after="0" w:line="240" w:lineRule="auto"/></w:pPr>`
30
+ * around the mark run, in the document's default paragraph style.
31
+ */
32
+ const SEPARATOR_STORY = [{
33
+ type: "paragraph",
34
+ formatting: {
35
+ spaceAfter: 0,
36
+ lineSpacing: 240,
37
+ lineSpacingRule: "auto"
38
+ },
39
+ content: []
40
+ }];
41
+ /**
42
+ * Convert and measure every referenced endnote into the areas `w:pos` asks
43
+ * for. `undefined` when no reference names a note, so a document without
44
+ * endnotes lays out exactly as it did before.
45
+ */
46
+ function prepareEndnoteAreas(input) {
47
+ const notesById = /* @__PURE__ */ new Map();
48
+ for (const note of input.endnotes ?? []) if (note.noteType === "normal") notesById.set(note.id, note);
49
+ if (notesById.size === 0 || input.displayNumbers.size === 0) return;
50
+ const finalSection = countSectionBreaks(input.blocks);
51
+ const referenceSection = firstReferenceSections(input.blocks);
52
+ const notesBySection = /* @__PURE__ */ new Map();
53
+ for (const noteId of input.displayNumbers.keys()) {
54
+ if (!notesById.has(noteId)) continue;
55
+ const section = input.position === "sectEnd" ? collectingSection(referenceSection.get(noteId) ?? finalSection, finalSection, input) : finalSection;
56
+ const noteIds = notesBySection.get(section) ?? [];
57
+ noteIds.push(noteId);
58
+ notesBySection.set(section, noteIds);
59
+ }
60
+ const separatorBlockIds = /* @__PURE__ */ new Map();
61
+ const contentBlockIds = /* @__PURE__ */ new Set();
62
+ const noteStoryByBlockId = /* @__PURE__ */ new Map();
63
+ const areas = [];
64
+ for (const [section, noteIds] of [...notesBySection].sort(([left], [right]) => left - right)) {
65
+ const width = endnoteAreaWidth(section === finalSection ? input.sections.at(-1) : input.sections[section]);
66
+ const separator = convertSeparatorStory(`${`endnote-area-${String(section)}`}-separator`, width, input.options);
67
+ const blocks = [{
68
+ ...separator.block,
69
+ attrs: {
70
+ ...separator.block.attrs,
71
+ keepNext: true
72
+ }
73
+ }];
74
+ const measures = [separator.measure];
75
+ separatorBlockIds.set(String(separator.block.id), "separator");
76
+ for (const noteId of noteIds) {
77
+ const content = convertFootnoteToContent(notesById.get(noteId), input.displayNumbers.get(noteId) ?? 0, width, {
78
+ ...input.options,
79
+ displayText: input.displayTexts.get(noteId) ?? String(noteId)
80
+ });
81
+ for (const [index, block] of content.blocks.entries()) {
82
+ const id = `endnote-${String(noteId)}-${String(index)}`;
83
+ blocks.push(withId(stripFlowBlockPmAnchors(block), id));
84
+ measures.push(content.measures[index]);
85
+ contentBlockIds.add(id);
86
+ noteStoryByBlockId.set(id, {
87
+ kind: "endnote",
88
+ noteId
89
+ });
90
+ }
91
+ }
92
+ areas.push({
93
+ beforeSectionBreak: section === finalSection ? null : section,
94
+ blocks: resolveEffectiveParagraphSpacingTree(blocks),
95
+ measures
96
+ });
97
+ }
98
+ const continuationSeparator = convertSeparatorStory("endnote-continuation-separator", endnoteAreaWidth(input.sections.at(-1)), input.options);
99
+ separatorBlockIds.set(String(continuationSeparator.block.id), "continuationSeparator");
100
+ return {
101
+ areas,
102
+ noteAreas: {
103
+ separatorBlockIds,
104
+ contentBlockIds,
105
+ continuationSeparator
106
+ },
107
+ noteStoryByBlockId
108
+ };
109
+ }
110
+ /**
111
+ * The body blocks with each endnote area in place: before the section break
112
+ * that ends the section it follows, or after the last body block.
113
+ */
114
+ function spliceEndnoteAreas(blocks, measures, prepared) {
115
+ if (prepared === void 0 || prepared.areas.length === 0) return {
116
+ blocks,
117
+ measures
118
+ };
119
+ const areasByBreak = /* @__PURE__ */ new Map();
120
+ let documentEndArea;
121
+ for (const area of prepared.areas) if (area.beforeSectionBreak === null) documentEndArea = area;
122
+ else areasByBreak.set(area.beforeSectionBreak, area);
123
+ const splicedBlocks = [];
124
+ const splicedMeasures = [];
125
+ let sectionBreakIndex = 0;
126
+ for (const [index, block] of blocks.entries()) {
127
+ if (block.kind === "sectionBreak") {
128
+ const area = areasByBreak.get(sectionBreakIndex);
129
+ if (area !== void 0) {
130
+ splicedBlocks.push(...area.blocks);
131
+ splicedMeasures.push(...area.measures);
132
+ }
133
+ sectionBreakIndex += 1;
134
+ }
135
+ splicedBlocks.push(block);
136
+ splicedMeasures.push(measures[index]);
137
+ }
138
+ if (documentEndArea !== void 0) {
139
+ splicedBlocks.push(...documentEndArea.blocks);
140
+ splicedMeasures.push(...documentEndArea.measures);
141
+ }
142
+ return {
143
+ blocks: splicedBlocks,
144
+ measures: splicedMeasures
145
+ };
146
+ }
147
+ /**
148
+ * The endnotes a `docEnd` area collects after the body's last block, for
149
+ * `toFlowBlocks`' `trailingEndnoteIds`. Empty for `sectEnd`, whose final area
150
+ * depends on where the references fall.
151
+ */
152
+ function trailingEndnoteIds(endnotes, position) {
153
+ if (position !== "docEnd") return /* @__PURE__ */ new Set();
154
+ return new Set((endnotes ?? []).flatMap((note) => note.noteType === "normal" ? [note.id] : []));
155
+ }
156
+ /** `w:pos` for the document: the settings value, else the final section's. */
157
+ function resolveEndnotePosition(settingsPosition, finalSection) {
158
+ return settingsPosition ?? finalSection?.endnotePr?.position ?? "docEnd";
159
+ }
160
+ /** The first column's width in a section: what an endnote area there wraps to. */
161
+ function endnoteAreaWidth(section) {
162
+ const pageSize = getPageSize(section);
163
+ const margins = getMargins(section);
164
+ const [firstColumn] = calculateColumnWidths(pageSize.w, margins.left, margins.right, getColumns(section) ?? {
165
+ count: 1,
166
+ gap: 0
167
+ });
168
+ return firstColumn ?? pageSize.w - margins.left - margins.right;
169
+ }
170
+ function countSectionBreaks(blocks) {
171
+ let count = 0;
172
+ for (const block of blocks) if (block.kind === "sectionBreak") count += 1;
173
+ return count;
174
+ }
175
+ /** The section each endnote is first referenced in. */
176
+ function firstReferenceSections(blocks) {
177
+ const sections = /* @__PURE__ */ new Map();
178
+ let section = 0;
179
+ for (const block of blocks) {
180
+ if (block.kind === "sectionBreak") {
181
+ section += 1;
182
+ continue;
183
+ }
184
+ for (const { endnoteId } of collectEndnoteRefs([block])) if (!sections.has(endnoteId)) sections.set(endnoteId, section);
185
+ }
186
+ return sections;
187
+ }
188
+ /**
189
+ * The section whose end collects a note referenced in `section`: a section
190
+ * with `w:noEndnote` passes its endnotes on to the next section that shows
191
+ * them, and the document's end collects whatever is left.
192
+ */
193
+ function collectingSection(section, finalSection, input) {
194
+ let target = section;
195
+ while (target < finalSection && input.sections[target]?.noEndnote === true) target += 1;
196
+ return target;
197
+ }
198
+ /** A separator story's paragraph, converted and measured as a note's text is. */
199
+ function convertSeparatorStory(id, width, options) {
200
+ const converted = convertNoteStoryToFlowBlocks(SEPARATOR_STORY, options).flowBlocks.at(0);
201
+ const block = converted?.kind === "paragraph" ? {
202
+ ...converted,
203
+ id
204
+ } : {
205
+ kind: "paragraph",
206
+ id,
207
+ runs: [{
208
+ kind: "text",
209
+ text: ""
210
+ }]
211
+ };
212
+ const [measure] = options.measureBlocks([block], width);
213
+ if (measure?.kind !== "paragraph") panic("An endnote separator paragraph must measure as a paragraph");
214
+ const { pmStart: _pmStart, pmEnd: _pmEnd, ...detached } = block;
215
+ return {
216
+ block: detached,
217
+ measure
218
+ };
219
+ }
220
+ function withId(block, id) {
221
+ return {
222
+ ...block,
223
+ id
224
+ };
225
+ }
226
+ //#endregion
227
+ export { endnoteAreaWidth, prepareEndnoteAreas, resolveEndnotePosition, spliceEndnoteAreas, trailingEndnoteIds };
@@ -15,6 +15,12 @@ type ConvertFootnoteOptions = {
15
15
  justificationCompatibility?: ToFlowBlocksOptions["justificationCompatibility"];
16
16
  tableIndentCompatibility?: ToFlowBlocksOptions["tableIndentCompatibility"];
17
17
  automaticHyphenation?: ToFlowBlocksOptions["automaticHyphenation"];
18
+ /**
19
+ * The note's reference text, when it is not the plain display number: an
20
+ * endnote's number in its `w:numFmt` (`i`, `ii`, …). The note shows the same
21
+ * text as its reference mark in the body.
22
+ */
23
+ displayText?: string;
18
24
  };
19
25
  /**
20
26
  * Scan FlowBlocks for runs with footnoteRefId set.
@@ -82,8 +88,18 @@ declare function mapFootnotesToPages(pages: Page[], footnoteRefs: {
82
88
  * Convert a Footnote's content paragraphs to FlowBlocks suitable for rendering.
83
89
  * Prepends the display number to the first run of the first paragraph.
84
90
  */
85
- declare function convertFootnoteToContent(footnote: document_d_exports.Footnote, displayNumber: number, contentWidth: number, options?: ConvertFootnoteOptions): FootnoteContent;
86
- declare function applyFootnotePresentation(blocks: FlowBlock[], displayNumber: number): FlowBlock[];
91
+ declare function convertFootnoteToContent(footnote: document_d_exports.Footnote | document_d_exports.Endnote, displayNumber: number, contentWidth: number, options?: ConvertFootnoteOptions): FootnoteContent;
92
+ /**
93
+ * A note story's content as flow blocks, through the same projection and
94
+ * conversion a note's text takes. `referenceMarkText` is what the story's own
95
+ * `w:footnoteRef`/`w:endnoteRef` mark shows; a story without one (a separator
96
+ * story) is converted as authored.
97
+ */
98
+ declare function convertNoteStoryToFlowBlocks(content: document_d_exports.Footnote["content"], options: ConvertFootnoteOptions, referenceMarkText?: string): {
99
+ flowBlocks: FlowBlock[];
100
+ hasReferenceMark: boolean;
101
+ };
102
+ declare function applyFootnotePresentation(blocks: FlowBlock[], displayNumber: number | string): FlowBlock[];
87
103
  /**
88
104
  * Build footnote content for all footnotes referenced in the document.
89
105
  * Returns a Map<footnoteId, FootnoteContent>.
@@ -99,4 +115,4 @@ declare function calculateFootnoteReservedHeights(pageFootnoteMap: Map<number, n
99
115
  height: number;
100
116
  }>): Map<number, number>;
101
117
  //#endregion
102
- export { ConvertFootnoteOptions, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, MeasureBlocksFn, NoteDisplayNumberMaps, applyFootnotePresentation, buildFootnoteContentMap, calculateFootnoteReservedHeights, collectEndnoteRefs, collectFootnoteRefs, computeNoteDisplayNumbers, convertFootnoteToContent, mapFootnotesToPages, remapNoteMarkerText };
118
+ export { ConvertFootnoteOptions, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, MeasureBlocksFn, NoteDisplayNumberMaps, applyFootnotePresentation, buildFootnoteContentMap, calculateFootnoteReservedHeights, collectEndnoteRefs, collectFootnoteRefs, computeNoteDisplayNumbers, convertFootnoteToContent, convertNoteStoryToFlowBlocks, mapFootnotesToPages, remapNoteMarkerText };
@@ -1,8 +1,9 @@
1
1
  import { measureParagraph } from "../../layout-engine/measure/measureParagraph.js";
2
2
  import { layoutTextBoxContent } from "../../layout-engine/measure/textBoxParagraphLayout.js";
3
3
  import { DEFAULT_TEXTBOX_MARGINS, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, isFloatingImageRun, resolveTableWidthPx } from "../../layout-engine/types.js";
4
+ import { expectPreservedXmlAttrs } from "../../prosemirror/attrs/index.js";
4
5
  import { footnoteToProseDoc } from "../../prosemirror/conversion/toProseDoc.js";
5
- import { toFlowBlocks } from "./toFlowBlocks.js";
6
+ import { collectNoteRefs, isNoteReferenceMarkXml, toFlowBlocks } from "./toFlowBlocks.js";
6
7
  //#region src/layout-bridge/convert/footnoteLayout.ts
7
8
  /** Default footnote font size in points */
8
9
  const FOOTNOTE_FONT_SIZE = 8;
@@ -32,23 +33,6 @@ function collectEndnoteRefs(blocks) {
32
33
  pmPos
33
34
  }));
34
35
  }
35
- function collectNoteRefs(blocks, idKey) {
36
- const refs = [];
37
- const walk = (containerBlocks) => {
38
- for (const block of containerBlocks) if (block.kind === "paragraph") for (const run of block.runs) {
39
- if (run.kind !== "text") continue;
40
- const noteId = run[idKey];
41
- if (noteId !== void 0) refs.push({
42
- noteId,
43
- pmPos: run.pmStart ?? 0
44
- });
45
- }
46
- else if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) walk(cell.blocks);
47
- else if (block.kind === "textBox") walk(block.content);
48
- };
49
- walk(blocks);
50
- return refs;
51
- }
52
36
  /**
53
37
  * Assign sequential display numbers (1, 2, 3, …) to notes in order of first
54
38
  * reference. Word numbers footnotes/endnotes by reference order, not by their
@@ -192,10 +176,33 @@ function mapFootnotesToPages(pages, footnoteRefs) {
192
176
  * Prepends the display number to the first run of the first paragraph.
193
177
  */
194
178
  function convertFootnoteToContent(footnote, displayNumber, contentWidth, options = {}) {
179
+ const displayText = options.displayText ?? String(displayNumber);
180
+ const { flowBlocks, hasReferenceMark } = convertNoteStoryToFlowBlocks(footnote.content, options, displayText);
181
+ const blocks = hasReferenceMark ? flowBlocks.map(applyFootnoteBlockPresentation) : applyFootnotePresentation(flowBlocks, displayText);
182
+ const measures = options.measureBlocks ? options.measureBlocks(blocks, contentWidth) : measureFootnoteBlocks(blocks, contentWidth);
183
+ let totalHeight = 0;
184
+ for (const measure of measures) if (measure.kind === "paragraph") totalHeight += measure.totalHeight;
185
+ else if (measure.kind === "table") totalHeight += measure.totalHeight;
186
+ else if (measure.kind === "image" || measure.kind === "textBox") totalHeight += measure.height;
187
+ return {
188
+ id: footnote.id,
189
+ displayNumber,
190
+ blocks,
191
+ measures,
192
+ height: totalHeight
193
+ };
194
+ }
195
+ /**
196
+ * A note story's content as flow blocks, through the same projection and
197
+ * conversion a note's text takes. `referenceMarkText` is what the story's own
198
+ * `w:footnoteRef`/`w:endnoteRef` mark shows; a story without one (a separator
199
+ * story) is converted as authored.
200
+ */
201
+ function convertNoteStoryToFlowBlocks(content, options, referenceMarkText) {
195
202
  const proseOptions = {};
196
203
  if (options.styles) proseOptions.styles = options.styles;
197
204
  if (options.theme !== void 0) proseOptions.theme = options.theme;
198
- const pmDoc = footnoteToProseDoc(footnote.content, proseOptions);
205
+ const pmDoc = footnoteToProseDoc(content, proseOptions);
199
206
  const flowOptions = {};
200
207
  if (options.defaultSize !== void 0) flowOptions.defaultSize = options.defaultSize;
201
208
  if (options.styles) flowOptions.styles = options.styles;
@@ -206,20 +213,22 @@ function convertFootnoteToContent(footnote, displayNumber, contentWidth, options
206
213
  if (options.justificationCompatibility) flowOptions.justificationCompatibility = options.justificationCompatibility;
207
214
  if (options.tableIndentCompatibility) flowOptions.tableIndentCompatibility = options.tableIndentCompatibility;
208
215
  if (options.automaticHyphenation) flowOptions.automaticHyphenation = options.automaticHyphenation;
209
- const blocks = applyFootnotePresentation(preserveAuthoredFootnoteTerminalParagraph(toFlowBlocks(pmDoc, flowOptions)), displayNumber);
210
- const measures = options.measureBlocks ? options.measureBlocks(blocks, contentWidth) : measureFootnoteBlocks(blocks, contentWidth);
211
- let totalHeight = 0;
212
- for (const measure of measures) if (measure.kind === "paragraph") totalHeight += measure.totalHeight;
213
- else if (measure.kind === "table") totalHeight += measure.totalHeight;
214
- else if (measure.kind === "image" || measure.kind === "textBox") totalHeight += measure.height;
216
+ const hasReferenceMark = containsNoteReferenceMark(pmDoc);
217
+ if (hasReferenceMark && referenceMarkText !== void 0) flowOptions.noteReferenceMarkText = referenceMarkText;
215
218
  return {
216
- id: footnote.id,
217
- displayNumber,
218
- blocks,
219
- measures,
220
- height: totalHeight
219
+ flowBlocks: preserveAuthoredFootnoteTerminalParagraph(toFlowBlocks(pmDoc, flowOptions)),
220
+ hasReferenceMark
221
221
  };
222
222
  }
223
+ function containsNoteReferenceMark(doc) {
224
+ let found = false;
225
+ doc.descendants((node) => {
226
+ if (found) return false;
227
+ if (node.type.name === "preservedXml" && isNoteReferenceMarkXml(expectPreservedXmlAttrs(node).xml)) found = true;
228
+ return !found;
229
+ });
230
+ return found;
231
+ }
223
232
  /** Restore an authored footnote line that the body-only terminal-table policy collapsed. */
224
233
  function preserveAuthoredFootnoteTerminalParagraph(blocks) {
225
234
  const finalBlock = blocks.at(-1);
@@ -488,4 +497,4 @@ function calculateFootnoteReservedHeights(pageFootnoteMap, footnoteContentMap) {
488
497
  return reserved;
489
498
  }
490
499
  //#endregion
491
- export { FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyFootnotePresentation, buildFootnoteContentMap, calculateFootnoteReservedHeights, collectEndnoteRefs, collectFootnoteRefs, computeNoteDisplayNumbers, convertFootnoteToContent, mapFootnotesToPages, remapNoteMarkerText };
500
+ export { FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyFootnotePresentation, buildFootnoteContentMap, calculateFootnoteReservedHeights, collectEndnoteRefs, collectFootnoteRefs, computeNoteDisplayNumbers, convertFootnoteToContent, convertNoteStoryToFlowBlocks, mapFootnotesToPages, remapNoteMarkerText };
@@ -13,12 +13,6 @@ type HeaderFooterMetrics = {
13
13
  margins: PageMargins;
14
14
  };
15
15
  declare function normalizeHeaderFooterMeasureBlocks(blocks: FlowBlock[], section?: HeaderFooterMetrics["section"]): FlowBlock[];
16
- /**
17
- * Header/footer auto-fit tables use `w:tblGrid` as a provisional ratio, not a
18
- * license to paint beyond the page furniture frame. Scale an oversized grid
19
- * for measurement while leaving the authored block untouched for round trips.
20
- */
21
- declare function fitHeaderFooterTablesToContentWidth(blocks: FlowBlock[], contentWidth: number): FlowBlock[];
22
16
  declare function resolveHeaderFooterPositionedVisualTop(position: ImageRunPosition | undefined, elementHeight: number, sourceY: number, flowHeight: number, metrics: HeaderFooterMetrics): number;
23
17
  declare function resolveHeaderFooterVisualTop(run: ImageRun, paragraphY: number, flowHeight: number, metrics: HeaderFooterMetrics): number;
24
18
  declare function calculateHeaderFooterVisualBounds(blocks: FlowBlock[], measures: Measure[], flowHeight: number, metrics: HeaderFooterMetrics): {
@@ -103,4 +97,4 @@ type ReserveHeaderFooterFullWidthWrapBandsOptions = {
103
97
  };
104
98
  declare function reserveHeaderFooterFullWidthWrapBands({ blocks, measures, contentWidth, metrics }: ReserveHeaderFooterFullWidthWrapBandsOptions): Measure[];
105
99
  //#endregion
106
- export { ConvertHeaderFooterOptions, HeaderFooterMetrics, calculateHeaderFooterBodyTopClearance, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, fitHeaderFooterTablesToContentWidth, normalizeHeaderFooterMeasureBlocks, reserveHeaderFooterFullWidthWrapBands, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
100
+ export { ConvertHeaderFooterOptions, HeaderFooterMetrics, calculateHeaderFooterBodyTopClearance, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, reserveHeaderFooterFullWidthWrapBands, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
@@ -1,5 +1,5 @@
1
1
  import { cloneParagraphWithPropertySource } from "../../docx/paragraphPropertySource.js";
2
- import { isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "../../layout-engine/types.js";
2
+ import { isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "../../layout-engine/types.js";
3
3
  import { headerFooterToProseDoc } from "../../prosemirror/conversion/toProseDoc.js";
4
4
  import { emuToPixels } from "../../utils/units.js";
5
5
  import { toFlowBlocks } from "./toFlowBlocks.js";
@@ -33,23 +33,6 @@ function preservesInheritedSpacing(block) {
33
33
  function normalizeHeaderFooterMeasureBlocks(blocks, section = "header") {
34
34
  return normalizeFlowBlockArray(blocks, { suppressTrailingEmptyAfterTable: section === "header" });
35
35
  }
36
- /**
37
- * Header/footer auto-fit tables use `w:tblGrid` as a provisional ratio, not a
38
- * license to paint beyond the page furniture frame. Scale an oversized grid
39
- * for measurement while leaving the authored block untouched for round trips.
40
- */
41
- function fitHeaderFooterTablesToContentWidth(blocks, contentWidth) {
42
- return blocks.map((block) => {
43
- if (block.kind !== "table" || block.floating !== void 0 || tableColumnsArePinned(block) || block.columnWidths === void 0) return block;
44
- const totalWidth = block.columnWidths.reduce((sum, width) => sum + width, 0);
45
- if (totalWidth <= contentWidth || totalWidth <= 0) return block;
46
- const scale = contentWidth / totalWidth;
47
- return {
48
- ...block,
49
- columnWidths: block.columnWidths.map((width) => width * scale)
50
- };
51
- });
52
- }
53
36
  function normalizeFlowBlockArray(blocks, normalization) {
54
37
  const trailingEmptyAfterTable = /* @__PURE__ */ new Set();
55
38
  const lastIndex = blocks.length - 1;
@@ -433,7 +416,7 @@ function reserveHeaderFooterFullWidthWrapBands({ blocks, measures, contentWidth,
433
416
  }
434
417
  function finalizeHeaderFooterContent(blocks, contentWidth, metrics, options) {
435
418
  if (blocks.length === 0) return;
436
- const blocksForMeasure = fitHeaderFooterTablesToContentWidth(normalizeHeaderFooterMeasureBlocks(blocks, metrics.section), contentWidth);
419
+ const blocksForMeasure = normalizeHeaderFooterMeasureBlocks(blocks, metrics.section);
437
420
  const measures = reserveHeaderFooterFullWidthWrapBands({
438
421
  blocks,
439
422
  measures: options.measureBlocks(blocksForMeasure, contentWidth),
@@ -547,6 +530,7 @@ function serializeParagraphAttrs(attrs) {
547
530
  "listMarkerFormatting",
548
531
  "listMarkerAlignment",
549
532
  "listMarkerSuffix",
533
+ "listLevelTabs",
550
534
  "tabs"
551
535
  ];
552
536
  const out = {};
@@ -575,6 +559,7 @@ function serializeRunFmt(run) {
575
559
  "alternateFontFamily",
576
560
  "eastAsiaFontFamily",
577
561
  "eastAsiaAlternateFontFamily",
562
+ "eastAsiaHint",
578
563
  "complexScriptFontFamily",
579
564
  "complexScriptAlternateFontFamily",
580
565
  "complexScriptFontSize",
@@ -594,4 +579,4 @@ function serializeRunFmt(run) {
594
579
  return JSON.stringify(out);
595
580
  }
596
581
  //#endregion
597
- export { calculateHeaderFooterBodyTopClearance, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, fitHeaderFooterTablesToContentWidth, normalizeHeaderFooterMeasureBlocks, reserveHeaderFooterFullWidthWrapBands, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
582
+ export { calculateHeaderFooterBodyTopClearance, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, reserveHeaderFooterFullWidthWrapBands, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
@@ -0,0 +1,10 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+ import { ParagraphAttrs } from "../../prosemirror/schema/nodes.js";
3
+ import { RunStyleResolver } from "../../prosemirror/runStyleFormatting.js";
4
+ //#region src/layout-bridge/convert/paragraphMarkFormatting.d.ts
5
+ /** The mark's own properties: its `w:rStyle` character style under its direct `w:rPr`. */
6
+ declare const resolveParagraphMarkOwnFormatting: (mark: document_d_exports.TextFormatting | undefined, styleResolver: RunStyleResolver) => document_d_exports.TextFormatting | undefined;
7
+ /** The paragraph mark's fully resolved run properties. */
8
+ declare const resolveParagraphMarkFormatting: (pmAttrs: ParagraphAttrs, styleResolver: RunStyleResolver) => document_d_exports.TextFormatting | undefined;
9
+ //#endregion
10
+ export { resolveParagraphMarkFormatting, resolveParagraphMarkOwnFormatting };
@@ -0,0 +1,59 @@
1
+ import { resolveParagraphBodyRunFormatting } from "../../prosemirror/runStyleFormatting.js";
2
+ import { cascadeStyleTextFormatting } from "../../prosemirror/styles/styleToggleCascade.js";
3
+ import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
4
+ //#region src/layout-bridge/convert/paragraphMarkFormatting.ts
5
+ /**
6
+ * Run properties of a paragraph mark (ECMA-376 §17.3.1.29 `w:pPr/w:rPr`).
7
+ *
8
+ * The mark resolves like any run of its paragraph: document defaults, the
9
+ * table style, the paragraph style chain, then a character style the mark
10
+ * names in `w:rStyle` (whose toggles combine per §17.7.3), then the mark's
11
+ * direct properties. The default character style is not applied to the mark.
12
+ */
13
+ /**
14
+ * The properties of the character style a mark names in `w:rStyle`. An id that
15
+ * is missing or names a style of another type contributes nothing.
16
+ */
17
+ const markCharacterStyleFormatting = (mark, styleResolver) => {
18
+ const styleId = mark?.styleId;
19
+ if (styleId === void 0 || styleResolver.getStyle(styleId)?.type !== "character") return;
20
+ return styleResolver.getRunStyleOwnProperties(styleId);
21
+ };
22
+ /** The mark's own properties: its `w:rStyle` character style under its direct `w:rPr`. */
23
+ const resolveParagraphMarkOwnFormatting = (mark, styleResolver) => {
24
+ const characterStyle = markCharacterStyleFormatting(mark, styleResolver);
25
+ if (characterStyle === void 0) return mark;
26
+ return cascadeStyleTextFormatting([{
27
+ formatting: characterStyle,
28
+ type: "style"
29
+ }, {
30
+ formatting: mark,
31
+ type: "direct"
32
+ }]).formatting;
33
+ };
34
+ /** The paragraph mark's fully resolved run properties. */
35
+ const resolveParagraphMarkFormatting = (pmAttrs, styleResolver) => {
36
+ const mark = pmAttrs._originalFormatting?.runProperties;
37
+ const { baseFormatting, baseToggleCascade } = resolveParagraphBodyRunFormatting({
38
+ styleId: pmAttrs.styleId ?? void 0,
39
+ styleResolver,
40
+ tableRunFormatting: pmAttrs._tableRunFormatting
41
+ });
42
+ const characterStyle = markCharacterStyleFormatting(mark, styleResolver);
43
+ return cascadeStyleTextFormatting([
44
+ {
45
+ cascade: baseToggleCascade,
46
+ type: "carried"
47
+ },
48
+ {
49
+ formatting: characterStyle,
50
+ type: "style"
51
+ },
52
+ {
53
+ formatting: mark,
54
+ type: "direct"
55
+ }
56
+ ], { ordinaryFormatting: mergeTextFormatting(mergeTextFormatting(baseFormatting, characterStyle), mark) }).formatting;
57
+ };
58
+ //#endregion
59
+ export { resolveParagraphMarkFormatting, resolveParagraphMarkOwnFormatting };
@@ -64,11 +64,39 @@ type ToFlowBlocksOptions = {
64
64
  justificationCompatibility?: NonNullable<ParagraphAttrs["justificationCompatibility"]>;
65
65
  /** Document-generation policy for where `w:tblInd` is measured from. */
66
66
  tableIndentCompatibility?: NonNullable<TableBlock["indentCompatibility"]>;
67
+ /**
68
+ * When set, every table-cell anchor lays out inside its cell regardless of
69
+ * an authored `wp:anchor/@layoutInCell="0"`. Compatibility mode 15 and
70
+ * above ignore that opt-out; see `resolveAnchorLayoutInCellCompatibility`.
71
+ */
72
+ forceAnchorLayoutInCell?: boolean;
67
73
  /** Document-wide automatic hyphenation policy. */
68
74
  automaticHyphenation?: NonNullable<ParagraphAttrs["automaticHyphenation"]>;
69
75
  /** Line pitch for the final body section, whose properties live outside the PM body. */
70
76
  finalSectionDocumentGridLinePitchTwips?: number;
77
+ /**
78
+ * The number a note story's `w:footnoteRef`/`w:endnoteRef` marks show. The
79
+ * mark stands for its note's reference number and shows it where it sits
80
+ * among the story's runs, in that run's formatting.
81
+ */
82
+ noteReferenceMarkText?: string;
83
+ /**
84
+ * Endnotes laid out after the body's last block (`w:pos="docEnd"`). A body
85
+ * that references one ends in its endnote area, so its final paragraph is no
86
+ * longer the document's last and keeps its height.
87
+ */
88
+ trailingEndnoteIds?: ReadonlySet<number>;
71
89
  };
90
+ /**
91
+ * Runs with `idKey` set, in document order: the note references a block list
92
+ * makes, through table cells (and tables within cells) and text boxes.
93
+ */
94
+ declare function collectNoteRefs(blocks: readonly FlowBlock[], idKey: "footnoteRefId" | "endnoteRefId"): {
95
+ noteId: number;
96
+ pmPos: number;
97
+ }[];
98
+ /** Whether captured run-child markup is a note story's `w:footnoteRef`/`w:endnoteRef`. */
99
+ declare function isNoteReferenceMarkXml(xml: string): boolean;
72
100
  /**
73
101
  * Word's layout fallback when an imported OOXML style hierarchy never supplies
74
102
  * `w:sz`. ECMA-376 deliberately leaves that terminal fallback to the consumer;
@@ -101,4 +129,4 @@ declare function convertBorderSpecToLayout(border: ConvertibleBorder, theme?: do
101
129
  */
102
130
  declare function toFlowBlocks(doc: Node, options?: ToFlowBlocksOptions): FlowBlock[];
103
131
  //#endregion
104
- export { ToFlowBlocksOptions, WORD_UNSPECIFIED_FONT_SIZE, convertBorderSpecToLayout, formatCounter, resetBlockIdCounter, resolveListTemplate, toFlowBlocks };
132
+ export { ToFlowBlocksOptions, WORD_UNSPECIFIED_FONT_SIZE, collectNoteRefs, convertBorderSpecToLayout, formatCounter, isNoteReferenceMarkXml, resetBlockIdCounter, resolveListTemplate, toFlowBlocks };