@stll/folio-core 0.33.2 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/apply.js +137 -23
- package/dist/ai-edits/snapshot.js +24 -2
- package/dist/ai-edits/types.d.ts +6 -6
- package/dist/compare/formatting.d.ts +1 -1
- package/dist/compare/formatting.js +38 -9
- package/dist/compare/verification.js +18 -1
- package/dist/controller/headerFooterEditorManager.js +11 -9
- package/dist/controller/layoutPipeline.js +24 -3
- package/dist/display-list/build/watermarkPrimitives.js +15 -3
- package/dist/document-operations.js +30 -3
- package/dist/docx/headerFooterParser.js +8 -14
- package/dist/docx/paragraphParser.js +45 -0
- package/dist/docx/serializer/headerFooterSerializer.js +21 -2
- package/dist/docx/serializer/paragraphSerializer.d.ts +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +19 -9
- package/dist/docx/settingsParser.js +3 -0
- package/dist/docx/watermarkParser.d.ts +2 -4
- package/dist/docx/watermarkParser.js +4 -6
- package/dist/headless-layout.js +14 -2
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -2
- package/dist/layout-bridge/convert/footnoteLayout.js +23 -10
- package/dist/layout-bridge/convert/headerFooterLayout.js +13 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +96 -13
- package/dist/layout-engine/index.js +11 -4
- package/dist/layout-engine/justifiedLineFit.d.ts +4 -4
- package/dist/layout-engine/justifiedLineFit.js +4 -4
- package/dist/layout-engine/measure/lineBreakProvider.js +1 -0
- package/dist/layout-engine/measure/measureBlocks.js +1 -1
- package/dist/layout-engine/measure/measureParagraph.js +29 -29
- package/dist/layout-painter/renderPage.js +6 -1
- package/dist/layout-painter/renderParagraph.js +18 -8
- package/dist/layout-painter/renderWatermark.js +11 -4
- package/dist/prosemirror/attrs/index.js +11 -0
- package/dist/prosemirror/commands/comments.js +22 -2
- package/dist/prosemirror/conversion/fromProseDoc.js +32 -5
- package/dist/prosemirror/conversion/toProseDoc.js +49 -11
- package/dist/prosemirror/extensions/core/DocExtension.js +7 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +1 -0
- package/dist/prosemirror/schema/marks.d.ts +2 -8
- package/dist/utils/fontResolver.js +51 -0
- package/dist/utils/formatToStyle.js +41 -1
- package/dist/watermark/index.js +7 -0
- package/package.json +2 -2
|
@@ -763,6 +763,47 @@ function parseSimpleField(node, styles, theme, rels, media, rootXmlns = {}) {
|
|
|
763
763
|
function hasRunPayloadElement(runElement) {
|
|
764
764
|
return getChildElements(runElement).some((child) => !matchesName(child, "w", "rPr"));
|
|
765
765
|
}
|
|
766
|
+
const LEGACY_FORM_CHECKBOX_GLYPHS = {
|
|
767
|
+
checked: "☒",
|
|
768
|
+
unchecked: "☐"
|
|
769
|
+
};
|
|
770
|
+
const LEGACY_FORM_CHECKBOX_INSTRUCTION = "FORMCHECKBOX";
|
|
771
|
+
function getLegacyFormCheckboxDisplay(runElement) {
|
|
772
|
+
const fieldChar = findChild(runElement, "w", "fldChar");
|
|
773
|
+
const fieldData = fieldChar ? findChild(fieldChar, "w", "ffData") : null;
|
|
774
|
+
const checkBox = fieldData ? findChild(fieldData, "w", "checkBox") : null;
|
|
775
|
+
if (!checkBox) return;
|
|
776
|
+
const checked = findChild(checkBox, "w", "checked");
|
|
777
|
+
const defaultChecked = findChild(checkBox, "w", "default");
|
|
778
|
+
let isChecked = defaultChecked ? parseBooleanElement(defaultChecked) : false;
|
|
779
|
+
if (checked) isChecked = parseBooleanElement(checked);
|
|
780
|
+
const explicitSize = parseNumericAttribute(findChild(checkBox, "w", "size"), "w", "val");
|
|
781
|
+
const text = isChecked ? LEGACY_FORM_CHECKBOX_GLYPHS.checked : LEGACY_FORM_CHECKBOX_GLYPHS.unchecked;
|
|
782
|
+
if (explicitSize === void 0) return { text };
|
|
783
|
+
return {
|
|
784
|
+
text,
|
|
785
|
+
fontSize: explicitSize
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function createLegacyFormCheckboxResultRun(display, inheritedFormatting) {
|
|
789
|
+
const run = {
|
|
790
|
+
type: "run",
|
|
791
|
+
content: [{
|
|
792
|
+
type: "text",
|
|
793
|
+
text: display.text
|
|
794
|
+
}]
|
|
795
|
+
};
|
|
796
|
+
const hasInheritedFormatting = inheritedFormatting !== void 0 && Object.keys(inheritedFormatting).length > 0;
|
|
797
|
+
if (display.fontSize !== void 0) run.formatting = hasInheritedFormatting ? {
|
|
798
|
+
...inheritedFormatting,
|
|
799
|
+
fontSize: display.fontSize
|
|
800
|
+
} : { fontSize: display.fontSize };
|
|
801
|
+
else if (hasInheritedFormatting) run.formatting = inheritedFormatting;
|
|
802
|
+
return run;
|
|
803
|
+
}
|
|
804
|
+
function isLegacyFormCheckboxInstruction(instruction) {
|
|
805
|
+
return instruction.trim().split(/\s+/u).at(0)?.toUpperCase() === LEGACY_FORM_CHECKBOX_INSTRUCTION;
|
|
806
|
+
}
|
|
766
807
|
/**
|
|
767
808
|
* Parse all content within a paragraph
|
|
768
809
|
*
|
|
@@ -779,6 +820,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
779
820
|
let afterSeparator = false;
|
|
780
821
|
let complexFieldLock = false;
|
|
781
822
|
let complexFieldDirty = false;
|
|
823
|
+
let complexFieldFallbackDisplay;
|
|
782
824
|
let complexFieldFormatting;
|
|
783
825
|
for (const child of children) {
|
|
784
826
|
const localName = getLocalName(child.name);
|
|
@@ -790,6 +832,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
790
832
|
let hasFieldBegin = false;
|
|
791
833
|
let beginFldLock = false;
|
|
792
834
|
let beginDirty = false;
|
|
835
|
+
const beginFallbackDisplay = getLegacyFormCheckboxDisplay(runElement);
|
|
793
836
|
let hasFieldSeparate = false;
|
|
794
837
|
let hasFieldEnd = false;
|
|
795
838
|
let endOriginalValue;
|
|
@@ -816,6 +859,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
816
859
|
complexFieldResultRuns = [];
|
|
817
860
|
complexFieldLock = beginFldLock;
|
|
818
861
|
complexFieldDirty = beginDirty;
|
|
862
|
+
complexFieldFallbackDisplay = beginFallbackDisplay;
|
|
819
863
|
complexFieldFormatting = run.formatting;
|
|
820
864
|
}
|
|
821
865
|
if (inComplexField) {
|
|
@@ -840,6 +884,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
840
884
|
}
|
|
841
885
|
if (hasFieldEnd) {
|
|
842
886
|
let resultRuns = complexFieldResultRuns;
|
|
887
|
+
if (resultRuns.length === 0 && complexFieldFallbackDisplay !== void 0 && isLegacyFormCheckboxInstruction(complexFieldInstr)) resultRuns = [createLegacyFormCheckboxResultRun(complexFieldFallbackDisplay, complexFieldFormatting)];
|
|
843
888
|
if (resultRuns.length === 0 && !afterSeparator && endOriginalValue !== void 0) resultRuns = [{
|
|
844
889
|
type: "run",
|
|
845
890
|
content: [{
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { canReplayHeaderFooterVerbatim, getHeaderFooterVerbatimXml } from "../headerFooterVerbatim.js";
|
|
2
|
+
import { isEmptyParagraph } from "../paragraphParser.js";
|
|
3
|
+
import { captureVerbatimXml } from "../verbatimCapture.js";
|
|
4
|
+
import { getLocalName, parseXmlDocument } from "../xmlParser.js";
|
|
2
5
|
import { serializeBlockSdt } from "./blockSdtSerializer.js";
|
|
3
6
|
import { serializeParagraph } from "./paragraphSerializer.js";
|
|
4
7
|
import { serializePartElement } from "./partNamespaces.js";
|
|
@@ -53,8 +56,16 @@ function serializeHeaderFooter(hf, source) {
|
|
|
53
56
|
const watermarkInsertIndex = hf.watermarkBlockIndex !== void 0 ? Math.max(0, Math.min(hf.watermarkBlockIndex, hf.content.length)) : 0;
|
|
54
57
|
const blocksXml = hf.content.map((block) => serializeBlock(block));
|
|
55
58
|
let contentXml;
|
|
56
|
-
if (watermarkXml)
|
|
57
|
-
|
|
59
|
+
if (watermarkXml) {
|
|
60
|
+
const watermarkHost = hf.content.at(watermarkInsertIndex);
|
|
61
|
+
const rawWatermarkXml = hf.rawWatermarkXml;
|
|
62
|
+
const mergesIntoRetainedHost = rawWatermarkXml !== void 0 && watermarkHost?.type === "paragraph" && isEmptyParagraph(watermarkHost);
|
|
63
|
+
if (mergesIntoRetainedHost) blocksXml[watermarkInsertIndex] = serializeRawWatermarkIntoHost({
|
|
64
|
+
hostXml: blocksXml[watermarkInsertIndex] ?? serializeParagraph(watermarkHost),
|
|
65
|
+
rawWatermarkXml
|
|
66
|
+
});
|
|
67
|
+
contentXml = blocksXml.slice(0, watermarkInsertIndex).join("") + (mergesIntoRetainedHost ? "" : watermarkXml) + blocksXml.slice(watermarkInsertIndex).join("");
|
|
68
|
+
} else contentXml = blocksXml.join("");
|
|
58
69
|
if (!contentXml) contentXml = "<w:p><w:pPr/></w:p>";
|
|
59
70
|
return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
|
|
60
71
|
partPath: source?.path ?? `word/${hf.type}.xml`,
|
|
@@ -64,6 +75,14 @@ function serializeHeaderFooter(hf, source) {
|
|
|
64
75
|
body: contentXml
|
|
65
76
|
});
|
|
66
77
|
}
|
|
78
|
+
function serializeRawWatermarkIntoHost({ hostXml, rawWatermarkXml }) {
|
|
79
|
+
const rawHost = parseXmlDocument(rawWatermarkXml);
|
|
80
|
+
if (!rawHost || getLocalName(rawHost.name ?? "") !== "p") return rawWatermarkXml;
|
|
81
|
+
const watermarkContent = (rawHost.elements ?? []).filter((child) => child.type === "element" && getLocalName(child.name ?? "") !== "pPr").map((child) => captureVerbatimXml(child)).join("");
|
|
82
|
+
if (!watermarkContent) return rawWatermarkXml;
|
|
83
|
+
const namespaceAttributes = Object.entries(rawHost.attributes ?? {}).filter(([name, value]) => value !== void 0 && (name === "xmlns" || name.startsWith("xmlns:"))).map(([name, value]) => ` ${name}="${escapeXml(String(value))}"`).join("");
|
|
84
|
+
return hostXml.replace(/^<w:p(?=[\s>])/u, `<w:p${namespaceAttributes}`).replace("</w:p>", `${watermarkContent}</w:p>`);
|
|
85
|
+
}
|
|
67
86
|
function serializeWatermarkParagraph(hf) {
|
|
68
87
|
if (hf.rawWatermarkXml) return hf.rawWatermarkXml;
|
|
69
88
|
if (hf.watermark) return synthesizeWatermarkParagraph(hf.watermark);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { document_d_exports } from "../../types/document.js";
|
|
2
2
|
//#region src/docx/serializer/paragraphSerializer.d.ts
|
|
3
|
-
declare function serializeParagraphFormatting(formatting: document_d_exports.ParagraphFormatting | undefined, propertyChanges?: document_d_exports.ParagraphPropertyChange[],
|
|
3
|
+
declare function serializeParagraphFormatting(formatting: document_d_exports.ParagraphFormatting | undefined, propertyChanges?: document_d_exports.ParagraphPropertyChange[], paragraphMarkChange?: document_d_exports.ParagraphMarkChange): string;
|
|
4
4
|
/**
|
|
5
5
|
* Serialize a paragraph to OOXML XML (w:p)
|
|
6
6
|
*
|
|
@@ -140,7 +140,7 @@ function serializeParagraphMarkChange(mark) {
|
|
|
140
140
|
const attrs = serializeTrackedChangeAttrs(mark.info);
|
|
141
141
|
return `<w:${mark.kind} ${attrs}/>`;
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
const serializeParagraphFormattingWithOptions = (formatting, { propertyChanges, paragraphMarkChange, sectionProperties } = {}) => {
|
|
144
144
|
const parts = [];
|
|
145
145
|
const pushToggle = (name, value) => {
|
|
146
146
|
if (value === true) parts.push(`<w:${name}/>`);
|
|
@@ -175,14 +175,22 @@ function serializeParagraphFormatting(formatting, propertyChanges, pPrMark) {
|
|
|
175
175
|
pushToggle("contextualSpacing", formatting.contextualSpacing);
|
|
176
176
|
if (formatting.alignment) parts.push(`<w:jc w:val="${formatting.alignment}"/>`);
|
|
177
177
|
if (formatting.outlineLevel !== void 0) parts.push(`<w:outlineLvl w:val="${formatting.outlineLevel}"/>`);
|
|
178
|
-
if (
|
|
179
|
-
const fullInner = `${
|
|
178
|
+
if (paragraphMarkChange || formatting.runProperties || formatting.runInWithNext) {
|
|
179
|
+
const fullInner = `${paragraphMarkChange ? serializeParagraphMarkChange(paragraphMarkChange) : ""}${formatting.runProperties ? extractRPrInner(serializeTextFormatting(formatting.runProperties)) : ""}${formatting.runInWithNext ? "<w:specVanish/>" : ""}`;
|
|
180
180
|
if (fullInner.length > 0) parts.push(`<w:rPr>${fullInner}</w:rPr>`);
|
|
181
181
|
}
|
|
182
|
-
} else if (
|
|
182
|
+
} else if (paragraphMarkChange) parts.push(`<w:rPr>${serializeParagraphMarkChange(paragraphMarkChange)}</w:rPr>`);
|
|
183
|
+
parts.push(serializeSectionProperties(sectionProperties));
|
|
183
184
|
if (propertyChanges && propertyChanges.length > 0) parts.push(...propertyChanges.map((change) => serializeParagraphPropertyChange(change)));
|
|
184
|
-
|
|
185
|
-
return
|
|
185
|
+
const inner = parts.join("");
|
|
186
|
+
if (inner.length === 0) return "";
|
|
187
|
+
return `<w:pPr>${inner}</w:pPr>`;
|
|
188
|
+
};
|
|
189
|
+
function serializeParagraphFormatting(formatting, propertyChanges, paragraphMarkChange) {
|
|
190
|
+
return serializeParagraphFormattingWithOptions(formatting, {
|
|
191
|
+
propertyChanges,
|
|
192
|
+
paragraphMarkChange
|
|
193
|
+
});
|
|
186
194
|
}
|
|
187
195
|
function extractPPrInner(pPrXml) {
|
|
188
196
|
if (!pPrXml.startsWith("<w:pPr>") || !pPrXml.endsWith("</w:pPr>")) return "";
|
|
@@ -486,9 +494,11 @@ function serializeParagraph(paragraph) {
|
|
|
486
494
|
if (paragraph.paraId) attrs.push(`w14:paraId="${escapeXml(paragraph.paraId)}"`);
|
|
487
495
|
if (paragraph.textId) attrs.push(`w14:textId="${escapeXml(paragraph.textId)}"`);
|
|
488
496
|
const attrsStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
497
|
+
parts.push(serializeParagraphFormattingWithOptions(paragraph.formatting, {
|
|
498
|
+
propertyChanges: paragraph.propertyChanges,
|
|
499
|
+
paragraphMarkChange: paragraph.pPrMark,
|
|
500
|
+
sectionProperties: paragraph.sectionProperties
|
|
501
|
+
}));
|
|
492
502
|
const explicitCommentReferenceIds = /* @__PURE__ */ new Set();
|
|
493
503
|
for (const content of paragraph.content) if (content.type === "commentReference") explicitCommentReferenceIds.add(content.id);
|
|
494
504
|
let pendingRenderedPageBreak = paragraph.renderedPageBreakBefore === true;
|
|
@@ -55,6 +55,9 @@ function parseSettings(xml) {
|
|
|
55
55
|
if (compatibilityMode !== void 0) settings.compatibilityMode = compatibilityMode;
|
|
56
56
|
const splitPageBreakAndParagraphMark = compat ? findChild(compat, "w", "splitPgBreakAndParaMark") : null;
|
|
57
57
|
if (splitPageBreakAndParagraphMark && parseBooleanElement(splitPageBreakAndParagraphMark)) settings.splitPageBreakAndParagraphMark = true;
|
|
58
|
+
const wordprocessingCompat = root ? findChildByNamespaceUri(root, WORDPROCESSINGML_NAMESPACE_URIS, "compat") : null;
|
|
59
|
+
const adjustLineHeightInTable = wordprocessingCompat ? findChildByNamespaceUri(wordprocessingCompat, WORDPROCESSINGML_NAMESPACE_URIS, "adjustLineHeightInTable") : null;
|
|
60
|
+
if (adjustLineHeightInTable && parseBooleanElement(adjustLineHeightInTable)) settings.adjustLineHeightInTable = true;
|
|
58
61
|
const applyBreakingRules = compat ? findChild(compat, "w", "applyBreakingRules") : null;
|
|
59
62
|
const useLegacyEthiopicAmharicRules = applyBreakingRules !== null && parseBooleanElement(applyBreakingRules);
|
|
60
63
|
if (noLineBreaksBefore || noLineBreaksAfter || useLegacyEthiopicAmharicRules) settings.lineBreakRules = {
|
|
@@ -14,10 +14,8 @@ type ParsedWatermark = {
|
|
|
14
14
|
hostingParagraph: XmlElement;
|
|
15
15
|
/**
|
|
16
16
|
* Index where the watermark paragraph sat among block-level siblings
|
|
17
|
-
* (`w:p` / `w:tbl`) in the source header.
|
|
18
|
-
*
|
|
19
|
-
* back at this index so a header that originally placed the
|
|
20
|
-
* watermark after visible text rounds-trips with the same flow.
|
|
17
|
+
* (`w:p` / `w:tbl` / `w:sdt`) in the source header. The serializer uses
|
|
18
|
+
* this index to replace the retained host paragraph with watermark XML.
|
|
21
19
|
*/
|
|
22
20
|
blockIndex: number;
|
|
23
21
|
};
|
|
@@ -59,11 +59,9 @@ function parseWatermark(header) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
|
-
* Index of `target` among block-level children of `header
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* serializer needs to splice the watermark XML back into so the
|
|
66
|
-
* header rounds-trips with the same flow as the source.
|
|
62
|
+
* Index of `target` among block-level children of `header`. Keep this list
|
|
63
|
+
* aligned with `parseBlockContent`, which emits paragraphs, tables, and
|
|
64
|
+
* block-level content controls as one content item each.
|
|
67
65
|
*/
|
|
68
66
|
function blockIndexOf(header, target) {
|
|
69
67
|
let blockIdx = 0;
|
|
@@ -71,7 +69,7 @@ function blockIndexOf(header, target) {
|
|
|
71
69
|
if (child === target) return blockIdx;
|
|
72
70
|
if (child.type !== "element") continue;
|
|
73
71
|
const local = getLocalName(child.name ?? "");
|
|
74
|
-
if (local === "p" || local === "tbl") blockIdx++;
|
|
72
|
+
if (local === "p" || local === "tbl" || local === "sdt") blockIdx++;
|
|
75
73
|
}
|
|
76
74
|
return 0;
|
|
77
75
|
}
|
package/dist/headless-layout.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveDocumentGridLinePitch } from "./docx/documentGrid.js";
|
|
2
|
+
import { formatOoxmlCounter } from "./docx/ooxmlCounterFormatter.js";
|
|
1
3
|
import { parseDocx } from "./docx/parser.js";
|
|
2
4
|
import { buildHeaderFooterFieldValues } from "./fields/resolveFieldValues.js";
|
|
3
5
|
import { extractEmbeddedFonts } from "./fonts/embeddedFonts.js";
|
|
@@ -50,6 +52,11 @@ import { createHash } from "node:crypto";
|
|
|
50
52
|
* backend in a browser); this module refuses to guess, because a silently
|
|
51
53
|
* wrong provider produces a plausible layout that is wrong everywhere.
|
|
52
54
|
*/
|
|
55
|
+
const formatEndnoteTexts = (numbers, formatNumber) => {
|
|
56
|
+
const texts = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const [id, displayNumber] of numbers) texts.set(id, formatNumber(displayNumber));
|
|
58
|
+
return texts;
|
|
59
|
+
};
|
|
53
60
|
var HeadlessLayoutError = class extends TaggedError("HeadlessLayoutError") {};
|
|
54
61
|
/**
|
|
55
62
|
* Stories this entry still does not lay out, keyed by story so the map is total
|
|
@@ -93,6 +100,8 @@ const buildFlowOptions = (document, pageContentHeight) => {
|
|
|
93
100
|
...settings.consecutiveHyphenLimit === void 0 ? {} : { consecutiveLineLimit: settings.consecutiveHyphenLimit },
|
|
94
101
|
...settings.hyphenationZoneTwips === void 0 ? {} : { hyphenationZoneTwips: settings.hyphenationZoneTwips }
|
|
95
102
|
};
|
|
103
|
+
const finalSectionDocumentGridLinePitchTwips = resolveDocumentGridLinePitch(document.package.document.sections?.at(-1)?.properties.docGrid);
|
|
104
|
+
if (finalSectionDocumentGridLinePitchTwips !== void 0) options.finalSectionDocumentGridLinePitchTwips = finalSectionDocumentGridLinePitchTwips;
|
|
96
105
|
return options;
|
|
97
106
|
};
|
|
98
107
|
/**
|
|
@@ -237,9 +246,12 @@ const layoutDocxHeadless = async (input, options = {}) => {
|
|
|
237
246
|
const footnotes = document.package.footnotes ?? [];
|
|
238
247
|
const endnotes = document.package.endnotes ?? [];
|
|
239
248
|
const footnoteRefs = collectFootnoteRefs(authored);
|
|
249
|
+
const footnoteNumbers = computeNoteDisplayNumbers(footnotes, footnoteRefs.map((ref) => ref.footnoteId));
|
|
250
|
+
const endnoteNumbers = computeNoteDisplayNumbers(endnotes, collectEndnoteRefs(authored).map((ref) => ref.endnoteId));
|
|
251
|
+
const endnoteNumberFormat = finalSection?.endnotePr?.numFmt ?? "lowerRoman";
|
|
240
252
|
const blocks = remapNoteMarkerText(authored, {
|
|
241
|
-
footnoteNumbers
|
|
242
|
-
|
|
253
|
+
footnoteNumbers,
|
|
254
|
+
endnoteTexts: formatEndnoteTexts(endnoteNumbers, (displayNumber) => formatOoxmlCounter(displayNumber, endnoteNumberFormat))
|
|
243
255
|
});
|
|
244
256
|
const measures = measureBlocks(blocks, contentWidth);
|
|
245
257
|
const footnoteContentById = footnoteRefs.length === 0 ? void 0 : buildFootnoteContentMap(footnotes, footnoteRefs, contentWidth, {
|
|
@@ -52,8 +52,8 @@ declare function computeNoteDisplayNumbers(notes: readonly NumberableNote[], ref
|
|
|
52
52
|
type NoteDisplayNumberMaps = {
|
|
53
53
|
/** footnote `w:id` → sequential display number */
|
|
54
54
|
footnoteNumbers?: ReadonlyMap<number, number>;
|
|
55
|
-
/** endnote `w:id` →
|
|
56
|
-
|
|
55
|
+
/** endnote `w:id` → formatted display text */
|
|
56
|
+
endnoteTexts?: ReadonlyMap<number, string>;
|
|
57
57
|
};
|
|
58
58
|
/**
|
|
59
59
|
* Rewrite body reference-marker run text from the raw `w:id` (which the PM
|
|
@@ -80,7 +80,7 @@ function computeNoteDisplayNumbers(notes, refNoteIds) {
|
|
|
80
80
|
* template preview substitution).
|
|
81
81
|
*/
|
|
82
82
|
function remapNoteMarkerText(blocks, maps) {
|
|
83
|
-
if ((maps.footnoteNumbers?.size ?? 0) === 0 && (maps.
|
|
83
|
+
if ((maps.footnoteNumbers?.size ?? 0) === 0 && (maps.endnoteTexts?.size ?? 0) === 0) return blocks;
|
|
84
84
|
let changed = false;
|
|
85
85
|
const next = blocks.map((block) => {
|
|
86
86
|
const remapped = remapNoteMarkerBlock(block, maps);
|
|
@@ -147,17 +147,19 @@ function remapNoteMarkerParagraph(block, maps) {
|
|
|
147
147
|
}
|
|
148
148
|
function remapNoteMarkerRun(run, maps) {
|
|
149
149
|
if (run.kind !== "text") return run;
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
return run.text === text ? run : {
|
|
150
|
+
const displayText = getRunDisplayText(run, maps);
|
|
151
|
+
if (displayText === void 0) return run;
|
|
152
|
+
return run.text === displayText ? run : {
|
|
154
153
|
...run,
|
|
155
|
-
text
|
|
154
|
+
text: displayText
|
|
156
155
|
};
|
|
157
156
|
}
|
|
158
|
-
function
|
|
159
|
-
if (run.footnoteRefId !== void 0)
|
|
160
|
-
|
|
157
|
+
function getRunDisplayText(run, maps) {
|
|
158
|
+
if (run.footnoteRefId !== void 0) {
|
|
159
|
+
const displayNumber = maps.footnoteNumbers?.get(run.footnoteRefId);
|
|
160
|
+
return displayNumber === void 0 ? void 0 : String(displayNumber);
|
|
161
|
+
}
|
|
162
|
+
if (run.endnoteRefId !== void 0) return maps.endnoteTexts?.get(run.endnoteRefId);
|
|
161
163
|
}
|
|
162
164
|
/**
|
|
163
165
|
* After layout, determine which footnotes appear on which pages.
|
|
@@ -202,7 +204,7 @@ function convertFootnoteToContent(footnote, displayNumber, contentWidth, options
|
|
|
202
204
|
if (options.justificationCompatibility) flowOptions.justificationCompatibility = options.justificationCompatibility;
|
|
203
205
|
if (options.tableIndentCompatibility) flowOptions.tableIndentCompatibility = options.tableIndentCompatibility;
|
|
204
206
|
if (options.automaticHyphenation) flowOptions.automaticHyphenation = options.automaticHyphenation;
|
|
205
|
-
const blocks = applyFootnotePresentation(toFlowBlocks(pmDoc, flowOptions), displayNumber);
|
|
207
|
+
const blocks = applyFootnotePresentation(preserveAuthoredFootnoteTerminalParagraph(toFlowBlocks(pmDoc, flowOptions)), displayNumber);
|
|
206
208
|
const measures = options.measureBlocks ? options.measureBlocks(blocks, contentWidth) : measureFootnoteBlocks(blocks, contentWidth);
|
|
207
209
|
let totalHeight = 0;
|
|
208
210
|
for (const measure of measures) if (measure.kind === "paragraph") totalHeight += measure.totalHeight;
|
|
@@ -216,6 +218,17 @@ function convertFootnoteToContent(footnote, displayNumber, contentWidth, options
|
|
|
216
218
|
height: totalHeight
|
|
217
219
|
};
|
|
218
220
|
}
|
|
221
|
+
/** Restore an authored footnote line that the body-only terminal-table policy collapsed. */
|
|
222
|
+
function preserveAuthoredFootnoteTerminalParagraph(blocks) {
|
|
223
|
+
const finalBlock = blocks.at(-1);
|
|
224
|
+
if (blocks.at(-2)?.kind !== "table" || finalBlock?.kind !== "paragraph" || finalBlock.attrs?.suppressEmptyParagraphHeight !== true) return blocks;
|
|
225
|
+
const attrs = { ...finalBlock.attrs };
|
|
226
|
+
delete attrs.suppressEmptyParagraphHeight;
|
|
227
|
+
return [...blocks.slice(0, -1), {
|
|
228
|
+
...finalBlock,
|
|
229
|
+
attrs
|
|
230
|
+
}];
|
|
231
|
+
}
|
|
219
232
|
function measureFootnoteBlocks(blocks, contentWidth) {
|
|
220
233
|
return blocks.map((block) => measureFootnoteBlock(block, contentWidth));
|
|
221
234
|
}
|
|
@@ -3,6 +3,16 @@ import { headerFooterToProseDoc } from "../../prosemirror/conversion/toProseDoc.
|
|
|
3
3
|
import { emuToPixels } from "../../utils/units.js";
|
|
4
4
|
import { toFlowBlocks } from "./toFlowBlocks.js";
|
|
5
5
|
//#region src/layout-bridge/convert/headerFooterLayout.ts
|
|
6
|
+
const DETACHED_WATERMARK_HOST = Symbol.for("stll.detachedWatermarkHost");
|
|
7
|
+
const headerFooterToProseDocWithDetachedWatermarkHost = (headerFooter, options) => {
|
|
8
|
+
return headerFooterToProseDoc(headerFooter.content.map((block, blockIndex) => {
|
|
9
|
+
if (blockIndex !== headerFooter.watermarkBlockIndex || block.type !== "paragraph") return block;
|
|
10
|
+
return {
|
|
11
|
+
...block,
|
|
12
|
+
[DETACHED_WATERMARK_HOST]: true
|
|
13
|
+
};
|
|
14
|
+
}), options);
|
|
15
|
+
};
|
|
6
16
|
function isAnchoredImageRun(run) {
|
|
7
17
|
if (run.kind !== "image") return false;
|
|
8
18
|
if (run.position) return true;
|
|
@@ -241,7 +251,7 @@ function calculateHeaderFooterVisualBounds(blocks, measures, flowHeight, metrics
|
|
|
241
251
|
* these bounds separately describe the in-flow portion for API consumers.
|
|
242
252
|
*/
|
|
243
253
|
function calculateHeaderFooterMarginPushBounds(blocks, measures, flowHeight, metrics) {
|
|
244
|
-
if (blocks.length > 0 && blocks.every((block) => isPaintlessParagraph(block) && !hasAuthoredVisualContent(block))) return {
|
|
254
|
+
if (blocks.length > 0 && blocks.every((block) => block.kind === "paragraph" && isPaintlessParagraph(block) && !hasAuthoredVisualContent(block) && block.attrs?.suppressEmptyParagraphHeight !== false && !preservesInheritedSpacing(block))) return {
|
|
245
255
|
top: 0,
|
|
246
256
|
bottom: 0
|
|
247
257
|
};
|
|
@@ -330,7 +340,7 @@ function convertHeaderFooterToContent(headerFooter, contentWidth, metrics, optio
|
|
|
330
340
|
const proseDocOptions = {};
|
|
331
341
|
if (options.styles) proseDocOptions.styles = options.styles;
|
|
332
342
|
if (options.theme !== void 0) proseDocOptions.theme = options.theme;
|
|
333
|
-
const pmDoc = headerFooterToProseDoc(headerFooter.content, proseDocOptions);
|
|
343
|
+
const pmDoc = headerFooter.watermarkBlockIndex === void 0 ? headerFooterToProseDoc(headerFooter.content, proseDocOptions) : headerFooterToProseDocWithDetachedWatermarkHost(headerFooter, proseDocOptions);
|
|
334
344
|
const flowOptions = {};
|
|
335
345
|
if (options.theme !== void 0) flowOptions.theme = options.theme;
|
|
336
346
|
if (options.fontAlternates !== void 0) flowOptions.fontAlternates = options.fontAlternates;
|
|
@@ -458,6 +468,7 @@ function serializeParagraphAttrs(attrs) {
|
|
|
458
468
|
const keys = [
|
|
459
469
|
"alignment",
|
|
460
470
|
"bidi",
|
|
471
|
+
"suppressEmptyParagraphHeight",
|
|
461
472
|
"indent",
|
|
462
473
|
"spacing",
|
|
463
474
|
"styleId",
|
|
@@ -13,6 +13,7 @@ import { advanceListMarker, advanceVisibleListMarker, cloneListCounterState, for
|
|
|
13
13
|
import { resolveNumberedRefFields } from "../../prosemirror/numberedRefFields.js";
|
|
14
14
|
import { directionToBidi } from "../../prosemirror/paragraphDirection.js";
|
|
15
15
|
import { cascadeStyleTextFormatting } from "../../prosemirror/styles/styleToggleCascade.js";
|
|
16
|
+
import { expectTextBoxAnchorAttrs } from "../../prosemirror/textBoxAnchorAttrs.js";
|
|
16
17
|
import { assertValidProseMirrorDocument } from "../../prosemirror/validation.js";
|
|
17
18
|
import { normalizeShapeTextAnchor } from "../../types/documentEnumValues.js";
|
|
18
19
|
import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.js";
|
|
@@ -20,12 +21,22 @@ import { resolveThemeFont } from "../../utils/fontResolver.js";
|
|
|
20
21
|
import { resolveShadingFill } from "../../utils/formatToStyle.js";
|
|
21
22
|
import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
|
|
22
23
|
import { decodeOoxmlSymbolCharacter } from "../../utils/ooxmlSymbol.js";
|
|
24
|
+
import { sanitizeImageSrc } from "../../utils/sanitizeImageSrc.js";
|
|
23
25
|
import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
|
|
24
26
|
import { AUTO_PARAGRAPH_SPACING_PX, halfPointsToPixels, halfPointsToPoints, pointsToPixels } from "../../utils/units.js";
|
|
25
27
|
import { getColumns } from "../sectionColumns.js";
|
|
26
28
|
import { groupParagraphFrames } from "./paragraphFrames.js";
|
|
29
|
+
import { panic } from "better-result";
|
|
27
30
|
//#region src/layout-bridge/convert/toFlowBlocks.ts
|
|
31
|
+
const DETACHED_WATERMARK_HOST_ATTR = "_detachedWatermarkHost";
|
|
32
|
+
const expectDetachedWatermarkHostAttr = (attrs) => {
|
|
33
|
+
const value = Reflect.get(attrs, DETACHED_WATERMARK_HOST_ATTR);
|
|
34
|
+
if (value === null || value === void 0) return false;
|
|
35
|
+
if (typeof value !== "boolean") panic("Invalid ProseMirror detached watermark host attrs:\nparagraph.attrs._detachedWatermarkHost: Expected a boolean.");
|
|
36
|
+
return value;
|
|
37
|
+
};
|
|
28
38
|
const DEFAULT_FONT = "Calibri";
|
|
39
|
+
const TEXT_BOX_ANCHOR_BLOCK_ID = Symbol.for("stll.textBoxAnchorBlockId");
|
|
29
40
|
const DEFAULT_TABLE_CELL_MARGIN_TWIPS = {
|
|
30
41
|
top: 0,
|
|
31
42
|
right: 108,
|
|
@@ -454,6 +465,9 @@ function buildImageRun(attrs, constrained, pmStart, pmEnd, trackedChange) {
|
|
|
454
465
|
if (trackedChange?.changeRevisionId !== void 0) run.changeRevisionId = trackedChange.changeRevisionId;
|
|
455
466
|
return run;
|
|
456
467
|
}
|
|
468
|
+
/** A package image whose bytes cannot paint still owns its authored line box. */
|
|
469
|
+
const hasRelationshipBackedImageBox = (attrs) => typeof attrs.rId === "string" && attrs.rId.trim().length > 0 && typeof attrs.width === "number" && Number.isFinite(attrs.width) && attrs.width >= 0 && typeof attrs.height === "number" && Number.isFinite(attrs.height) && attrs.height >= 0;
|
|
470
|
+
const hasPaintableImageSource = (src) => sanitizeImageSrc(src) !== void 0;
|
|
457
471
|
/**
|
|
458
472
|
* In TOC paragraphs, strip the resolved Hyperlink character-style colour and
|
|
459
473
|
* underline so the painter's link fallback doesn't fire. The PM doc keeps the
|
|
@@ -564,7 +578,7 @@ function paragraphToRuns(node, startPos, _options) {
|
|
|
564
578
|
}
|
|
565
579
|
if (child.type.name === "image") {
|
|
566
580
|
const attrs = expectImageAttrs(child);
|
|
567
|
-
if (!attrs.src) return;
|
|
581
|
+
if (!hasPaintableImageSource(attrs.src) && !hasRelationshipBackedImageBox(attrs)) return;
|
|
568
582
|
const constrained = constrainImageToPage(attrs.width ?? 100, attrs.height ?? 100, _options.pageContentHeight);
|
|
569
583
|
const trackedFmt = extractRunFormatting(child.marks, theme, fontAlternates);
|
|
570
584
|
const run = buildImageRun(attrs, constrained, childPos, childPos + child.nodeSize, trackedFmt);
|
|
@@ -878,6 +892,9 @@ function mapTabAlignment(align) {
|
|
|
878
892
|
function hasOnlyVisuallyEmptyTextRuns(runs) {
|
|
879
893
|
return runs.length > 0 && runs.every((run) => run.kind === "text" && run.text.replace(/\u00a0/gu, " ").trim().length === 0);
|
|
880
894
|
}
|
|
895
|
+
function hasOnlyHiddenTextRuns(runs) {
|
|
896
|
+
return runs.length > 0 && runs.every((run) => run.kind === "text" && run.hidden === true);
|
|
897
|
+
}
|
|
881
898
|
function convertParagraph(node, startPos, options) {
|
|
882
899
|
const pmAttrs = expectParagraphAttrs(node);
|
|
883
900
|
const runs = paragraphToRuns(node, startPos, options);
|
|
@@ -903,10 +920,10 @@ function convertParagraph(node, startPos, options) {
|
|
|
903
920
|
if (alternate) attrs.defaultAlternateFontFamily = alternate;
|
|
904
921
|
}
|
|
905
922
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
923
|
+
const isFullyHiddenParagraph = defaultTextFormatting?.hidden === true && (runs.length === 0 || hasOnlyHiddenTextRuns(runs));
|
|
924
|
+
if (isFullyHiddenParagraph && attrs.listMarker !== void 0) attrs.listMarkerHidden = true;
|
|
925
|
+
if (isFullyHiddenParagraph) attrs.suppressEmptyParagraphHeight = true;
|
|
926
|
+
if (runs.length === 0 && expectDetachedWatermarkHostAttr(node.attrs)) attrs.suppressEmptyParagraphHeight = false;
|
|
910
927
|
const hasVisibleParagraphPayload = 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;
|
|
911
928
|
if (runs.length === 0 && pmAttrs._pageBreakCarrier === true && !hasVisibleParagraphPayload) attrs.suppressEmptyParagraphHeight = true;
|
|
912
929
|
const bookmarkNames = pmAttrs.bookmarks?.map((b) => b.name);
|
|
@@ -934,6 +951,11 @@ function convertParagraph(node, startPos, options) {
|
|
|
934
951
|
...frame.yAlign !== void 0 ? { yAlign: frame.yAlign } : {},
|
|
935
952
|
...frame.wrap !== void 0 ? { wrap: frame.wrap } : {}
|
|
936
953
|
});
|
|
954
|
+
node.descendants((child) => {
|
|
955
|
+
if (child.type.name !== "textBoxAnchor") return true;
|
|
956
|
+
options.textBoxAnchorBlockIds.set(expectTextBoxAnchorAttrs(child).anchorId, block.id);
|
|
957
|
+
return false;
|
|
958
|
+
});
|
|
937
959
|
return block;
|
|
938
960
|
}
|
|
939
961
|
/**
|
|
@@ -945,6 +967,7 @@ function convertParagraph(node, startPos, options) {
|
|
|
945
967
|
function isPaintlessTerminalParagraph(block) {
|
|
946
968
|
if (block?.kind !== "paragraph" || block.runs.length !== 0) return false;
|
|
947
969
|
const attrs = block.attrs;
|
|
970
|
+
if (attrs?.suppressEmptyParagraphHeight === false) return false;
|
|
948
971
|
return !(attrs?.listMarker !== void 0 && !attrs.listMarkerHidden || attrs?.borders?.top || attrs?.borders?.bottom || attrs?.borders?.left || attrs?.borders?.right || attrs?.borders?.between || attrs?.borders?.bar || attrs?.shading || attrs?.spacingExplicit?.before || attrs?.spacingExplicit?.after || attrs?.pageBreakBefore || attrs?.renderedPageBreakBefore);
|
|
949
972
|
}
|
|
950
973
|
function suppressFinalEmptyParagraphAfterTable(blocks) {
|
|
@@ -1188,6 +1211,7 @@ function convertTable(node, startPos, options) {
|
|
|
1188
1211
|
*/
|
|
1189
1212
|
function convertImage(node, startPos, pageContentHeight) {
|
|
1190
1213
|
const attrs = expectImageAttrs(node);
|
|
1214
|
+
if (!hasPaintableImageSource(attrs.src) && !hasRelationshipBackedImageBox(attrs)) return;
|
|
1191
1215
|
const wrapType = attrs.wrapType;
|
|
1192
1216
|
const shouldAnchor = wrapType === "behind" || wrapType === "inFront";
|
|
1193
1217
|
const constrained = constrainImageToPage(attrs.width ?? 100, attrs.height ?? 100, pageContentHeight);
|
|
@@ -1269,6 +1293,8 @@ function convertTextBoxNode(node, startPos, opts) {
|
|
|
1269
1293
|
if (attrs.distRight !== void 0) textBox.distRight = attrs.distRight;
|
|
1270
1294
|
if (attrs.position !== void 0) textBox.position = attrs.position;
|
|
1271
1295
|
if (attrs._docxGroupId !== void 0) setTextBoxGroupId(textBox, attrs._docxGroupId);
|
|
1296
|
+
const anchorBlockId = attrs._docxAnchorId ? opts.textBoxAnchorBlockIds.get(attrs._docxAnchorId) : void 0;
|
|
1297
|
+
if (anchorBlockId !== void 0) Reflect.set(textBox, TEXT_BOX_ANCHOR_BLOCK_ID, anchorBlockId);
|
|
1272
1298
|
return textBox;
|
|
1273
1299
|
}
|
|
1274
1300
|
function getLastMapKey(map) {
|
|
@@ -1277,6 +1303,41 @@ function getLastMapKey(map) {
|
|
|
1277
1303
|
return lastKey;
|
|
1278
1304
|
}
|
|
1279
1305
|
/**
|
|
1306
|
+
* Translate section-owned start modes into the boundary-owned values consumed
|
|
1307
|
+
* by the paginator. Each flow section break carries the properties for the
|
|
1308
|
+
* section it ends, so its start mode belongs on the preceding boundary.
|
|
1309
|
+
*/
|
|
1310
|
+
function applySectionStartsToBoundaries(blocks, finalSectionStart) {
|
|
1311
|
+
const breakIndexes = [];
|
|
1312
|
+
for (let index = 0; index < blocks.length; index += 1) if (blocks[index]?.kind === "sectionBreak") breakIndexes.push(index);
|
|
1313
|
+
if (breakIndexes.length === 0) return [...blocks];
|
|
1314
|
+
const result = [...blocks];
|
|
1315
|
+
for (let index = 0; index < breakIndexes.length; index += 1) {
|
|
1316
|
+
const boundaryIndex = breakIndexes[index];
|
|
1317
|
+
if (boundaryIndex === void 0) continue;
|
|
1318
|
+
const boundary = blocks[boundaryIndex];
|
|
1319
|
+
if (boundary?.kind !== "sectionBreak") continue;
|
|
1320
|
+
const nextBoundaryIndex = breakIndexes[index + 1];
|
|
1321
|
+
const nextBoundary = nextBoundaryIndex === void 0 ? void 0 : blocks[nextBoundaryIndex];
|
|
1322
|
+
const nextStart = nextBoundary?.kind === "sectionBreak" ? nextBoundary.type : finalSectionStart;
|
|
1323
|
+
const translated = { ...boundary };
|
|
1324
|
+
if (nextStart === void 0) delete translated.type;
|
|
1325
|
+
else translated.type = nextStart;
|
|
1326
|
+
result[boundaryIndex] = translated;
|
|
1327
|
+
}
|
|
1328
|
+
return result;
|
|
1329
|
+
}
|
|
1330
|
+
function readFinalSectionStart(doc) {
|
|
1331
|
+
const sectionStart = doc.attrs["_finalSectionStart"];
|
|
1332
|
+
switch (sectionStart) {
|
|
1333
|
+
case "continuous":
|
|
1334
|
+
case "nextPage":
|
|
1335
|
+
case "oddPage":
|
|
1336
|
+
case "evenPage": return sectionStart;
|
|
1337
|
+
default: return;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1280
1341
|
* Convert a ProseMirror document to FlowBlock array.
|
|
1281
1342
|
*
|
|
1282
1343
|
* Walks the document tree, converting each node to the appropriate block type.
|
|
@@ -1315,6 +1376,7 @@ function toFlowBlocks(doc, options = {}) {
|
|
|
1315
1376
|
final: listCounterState,
|
|
1316
1377
|
original: originalListCounterState
|
|
1317
1378
|
},
|
|
1379
|
+
textBoxAnchorBlockIds: /* @__PURE__ */ new Map(),
|
|
1318
1380
|
numberedRefResults: resolveNumberedRefFields(doc, {
|
|
1319
1381
|
listCounterState: cloneListCounterState(listCounterState),
|
|
1320
1382
|
originalListCounterState: cloneListCounterState(originalListCounterState)
|
|
@@ -1521,9 +1583,11 @@ function toFlowBlocks(doc, options = {}) {
|
|
|
1521
1583
|
case "table":
|
|
1522
1584
|
trackedPush(convertTable(node, pos, opts));
|
|
1523
1585
|
break;
|
|
1524
|
-
case "image":
|
|
1525
|
-
|
|
1586
|
+
case "image": {
|
|
1587
|
+
const image = convertImage(node, pos, opts.pageContentHeight);
|
|
1588
|
+
if (image !== void 0) trackedPush(image);
|
|
1526
1589
|
break;
|
|
1590
|
+
}
|
|
1527
1591
|
case "textBox":
|
|
1528
1592
|
trackedPush(convertTextBoxNode(node, pos, opts));
|
|
1529
1593
|
break;
|
|
@@ -1548,24 +1612,43 @@ function toFlowBlocks(doc, options = {}) {
|
|
|
1548
1612
|
reserveLeadingEmptyOutlineHeight(blocks);
|
|
1549
1613
|
suppressFinalEmptyParagraphAfterTable(blocks);
|
|
1550
1614
|
suppressFinalParagraphInRepeatedEmptySuffix(blocks);
|
|
1551
|
-
|
|
1615
|
+
const mergedBlocks = mergeRunInParagraphs(blocks);
|
|
1616
|
+
const tableCellLinePitch = doc.attrs["_adjustLineHeightInTable"] === true ? "sectionGrid" : void 0;
|
|
1617
|
+
return groupParagraphFrames(applySectionStartsToBoundaries(applySectionDocumentGrid(mergedBlocks, {
|
|
1618
|
+
finalLinePitchTwips: opts.finalSectionDocumentGridLinePitchTwips,
|
|
1619
|
+
tableCellLinePitch
|
|
1620
|
+
}), readFinalSectionStart(doc)), nextBlockId);
|
|
1552
1621
|
}
|
|
1553
|
-
function applySectionDocumentGrid(blocks, finalLinePitchTwips) {
|
|
1622
|
+
function applySectionDocumentGrid(blocks, { finalLinePitchTwips, tableCellLinePitch }) {
|
|
1554
1623
|
const result = [...blocks];
|
|
1555
1624
|
let sectionStart = 0;
|
|
1556
1625
|
const stampSection = (end, linePitchTwips) => {
|
|
1557
1626
|
if (linePitchTwips === void 0 || linePitchTwips <= 0) return;
|
|
1558
1627
|
const linePitch = twipsToPixels(linePitchTwips);
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
if (block?.kind !== "paragraph") continue;
|
|
1562
|
-
result[index] = {
|
|
1628
|
+
const stampBlock = (block) => {
|
|
1629
|
+
if (block.kind === "paragraph") return {
|
|
1563
1630
|
...block,
|
|
1564
1631
|
attrs: {
|
|
1565
1632
|
...block.attrs,
|
|
1566
1633
|
documentGridLinePitch: linePitch
|
|
1567
1634
|
}
|
|
1568
1635
|
};
|
|
1636
|
+
if (block.kind !== "table" || tableCellLinePitch !== "sectionGrid") return block;
|
|
1637
|
+
return {
|
|
1638
|
+
...block,
|
|
1639
|
+
rows: block.rows.map((row) => ({
|
|
1640
|
+
...row,
|
|
1641
|
+
cells: row.cells.map((cell) => ({
|
|
1642
|
+
...cell,
|
|
1643
|
+
blocks: cell.blocks.map(stampBlock)
|
|
1644
|
+
}))
|
|
1645
|
+
}))
|
|
1646
|
+
};
|
|
1647
|
+
};
|
|
1648
|
+
for (let index = sectionStart; index < end; index += 1) {
|
|
1649
|
+
const block = result[index];
|
|
1650
|
+
if (!block) continue;
|
|
1651
|
+
result[index] = stampBlock(block);
|
|
1569
1652
|
}
|
|
1570
1653
|
};
|
|
1571
1654
|
for (let index = 0; index < blocks.length; index += 1) {
|