@stll/folio-core 0.4.0 → 0.6.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.d.ts +3 -9
- package/dist/ai-edits/apply.js +94 -14
- package/dist/ai-edits/blockRange.d.ts +33 -0
- package/dist/ai-edits/blockRange.js +58 -0
- package/dist/ai-edits/headless.d.ts +37 -4
- package/dist/ai-edits/headless.js +134 -16
- package/dist/ai-edits/index.d.ts +5 -3
- package/dist/ai-edits/index.js +3 -2
- package/dist/ai-edits/snapshot.d.ts +14 -2
- package/dist/ai-edits/snapshot.js +12 -1
- package/dist/ai-edits/types.d.ts +42 -3
- package/dist/compat/eigenpal.d.ts +3 -2
- package/dist/compat/eigenpal.js +6 -5
- package/dist/controller/layoutPipeline.d.ts +1 -0
- package/dist/controller/layoutPipeline.js +35 -137
- package/dist/controller/layoutScheduler.d.ts +2 -1
- package/dist/controller/layoutScheduler.js +6 -0
- package/dist/document-operations.d.ts +139 -0
- package/dist/document-operations.js +578 -0
- package/dist/docx/blockContentParser.js +17 -7
- package/dist/docx/encryption/agileDecryption.js +2 -1
- package/dist/docx/encryption/compoundFile.js +4 -2
- package/dist/docx/footnoteParser.d.ts +1 -17
- package/dist/docx/footnoteParser.js +1 -27
- package/dist/docx/groupDrawingParser.d.ts +8 -0
- package/dist/docx/groupDrawingParser.js +131 -0
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +8 -2
- package/dist/docx/paragraphParser.js +9 -3
- package/dist/docx/runParser.js +8 -1
- package/dist/docx/selectiveXmlPatch.js +4 -10
- package/dist/docx/serializer/numberingSerializer.js +3 -1
- package/dist/docx/settingsParser.d.ts +6 -3
- package/dist/docx/settingsParser.js +2 -0
- package/dist/docx/xmlParser.js +11 -4
- package/dist/i18n/messages/catalogs.gen.d.ts +1054 -34
- package/dist/i18n/messages/catalogs.gen.js +1122 -68
- package/dist/i18n/messages/messages.gen.d.ts +62 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -5
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +37 -1
- package/dist/layout-bridge/convert/footnoteLayout.js +138 -14
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +5 -7
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -7
- package/dist/layout-bridge/convert/toFlowBlocks.js +56 -19
- package/dist/layout-engine/index.d.ts +6 -5
- package/dist/layout-engine/index.js +143 -30
- package/dist/layout-engine/keep-together.d.ts +13 -8
- package/dist/layout-engine/keep-together.js +40 -25
- package/dist/layout-engine/layoutInstrumentation.d.ts +11 -1
- package/dist/layout-engine/layoutInstrumentation.js +10 -1
- package/dist/layout-engine/measure/measureBlocks.js +50 -18
- package/dist/layout-engine/measure/measureParagraph.js +61 -15
- package/dist/layout-engine/paginator.d.ts +2 -1
- package/dist/layout-engine/paginator.js +9 -3
- package/dist/layout-engine/textBoxGroup.d.ts +7 -0
- package/dist/layout-engine/textBoxGroup.js +8 -0
- package/dist/layout-engine/types.d.ts +30 -9
- package/dist/layout-engine/types.js +20 -2
- package/dist/layout-painter/renderPage.d.ts +15 -1
- package/dist/layout-painter/renderPage.js +27 -11
- package/dist/layout-painter/renderParagraph.js +18 -8
- package/dist/layout-painter/renderTable.js +10 -7
- package/dist/managers/DocumentLoaderManager.js +12 -5
- package/dist/model.js +1 -1
- package/dist/paged-layout/pageText.d.ts +14 -0
- package/dist/paged-layout/pageText.js +21 -0
- package/dist/prosemirror/attrs/index.js +2 -0
- package/dist/prosemirror/commands/comments.js +75 -5
- package/dist/prosemirror/commands/hyperlink.js +4 -2
- package/dist/prosemirror/commands/image.js +2 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +68 -0
- package/dist/prosemirror/commands/propertyChangeScope.js +242 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +14 -3
- package/dist/prosemirror/conversion/toProseDoc.js +29 -8
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +6 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +10 -11
- package/dist/prosemirror/findReplaceSelection.js +2 -1
- package/dist/prosemirror/schema/nodes.d.ts +10 -5
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/utils/tabCalculator.js +4 -2
- package/dist/redline.d.ts +20 -0
- package/dist/redline.js +127 -0
- package/dist/server.d.ts +7 -3
- package/dist/server.js +6 -2
- package/dist/symbols.d.ts +35 -0
- package/dist/symbols.js +512 -0
- package/dist/utils/clipboard.js +3 -3
- package/dist/utils/fontLoader.js +3 -0
- package/dist/utils/fontResolver.js +48 -1
- package/dist/utils/zoom.d.ts +21 -0
- package/dist/utils/zoom.js +21 -0
- package/dist/version-comparison.d.ts +92 -0
- package/dist/version-comparison.js +436 -0
- package/package.json +3 -3
- package/dist/paged-layout/headerFooterMargins.d.ts +0 -75
- package/dist/paged-layout/headerFooterMargins.js +0 -122
|
@@ -5,6 +5,7 @@ import { DEFAULT_TEXTBOX_MARGINS } from "../../layout-engine/types.js";
|
|
|
5
5
|
import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.js";
|
|
6
6
|
import { resolveShadingFill } from "../../utils/formatToStyle.js";
|
|
7
7
|
import { convertBulletToUnicode } from "../../docx/bulletMarkers.js";
|
|
8
|
+
import { setTextBoxGroupId } from "../../layout-engine/textBoxGroup.js";
|
|
8
9
|
import { directionIsRtl } from "../../prosemirror/paragraphDirection.js";
|
|
9
10
|
import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunShadingMarkAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../../prosemirror/attrs/index.js";
|
|
10
11
|
import { autospacingMatchesBase } from "../../prosemirror/autospacingBase.js";
|
|
@@ -100,7 +101,7 @@ function toLetter(value, upper) {
|
|
|
100
101
|
return String.fromCodePoint(baseCodePoint + zeroBased % 26).repeat(Math.floor(zeroBased / 26) + 1);
|
|
101
102
|
}
|
|
102
103
|
function formatCounter(value, format) {
|
|
103
|
-
if (value
|
|
104
|
+
if (!Number.isFinite(value)) return "";
|
|
104
105
|
switch (format) {
|
|
105
106
|
case "upperRoman": return toRoman(value, true);
|
|
106
107
|
case "lowerRoman": return toRoman(value, false);
|
|
@@ -119,7 +120,9 @@ function resolveListTemplate(template, counters, levelFormats, forceDecimal = fa
|
|
|
119
120
|
const { digit, punct = "" } = args.at(-1);
|
|
120
121
|
const index = Number.parseInt(digit, 10) - 1;
|
|
121
122
|
if (index < 0) return "";
|
|
122
|
-
const
|
|
123
|
+
const counter = counters[index];
|
|
124
|
+
if (counter === void 0 || Number.isNaN(counter)) return "";
|
|
125
|
+
const formatted = formatCounter(counter, forceDecimal ? "decimal" : levelFormats?.[index]);
|
|
123
126
|
return formatted ? `${formatted}${punct}` : "";
|
|
124
127
|
});
|
|
125
128
|
}
|
|
@@ -143,22 +146,28 @@ function computeListMarker(pmAttrs, listCounters, abstractCounters, seenNumIds)
|
|
|
143
146
|
}
|
|
144
147
|
if (pmAttrs.listIsBullet) return convertBulletToUnicode(pmAttrs.listMarker || "");
|
|
145
148
|
const level = pmAttrs.numPr?.ilvl ?? 0;
|
|
146
|
-
const counters = listCounters.get(numId) ?? Array.from({ length: 9 }, () =>
|
|
149
|
+
const counters = listCounters.get(numId) ?? Array.from({ length: 9 }, () => NaN);
|
|
147
150
|
const abstractNumId = pmAttrs.listAbstractNumId;
|
|
148
|
-
if (
|
|
149
|
-
const latestAbstractCounters = abstractCounters.get(abstractNumId);
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
if (level > 0) {
|
|
152
|
+
const latestAbstractCounters = abstractNumId === void 0 ? void 0 : abstractCounters.get(abstractNumId);
|
|
153
|
+
if (counters.slice(0, level).every(Number.isNaN)) for (let i = 0; i < level; i += 1) {
|
|
154
|
+
const latestCounter = latestAbstractCounters?.[i];
|
|
155
|
+
counters[i] = latestCounter !== void 0 && !Number.isNaN(latestCounter) ? latestCounter : pmAttrs.listLevelStarts?.[i] ?? 1;
|
|
156
|
+
}
|
|
152
157
|
}
|
|
153
158
|
const seenKey = `${numId}:${level}`;
|
|
154
159
|
if (!seenNumIds.has(seenKey)) {
|
|
155
160
|
seenNumIds.add(seenKey);
|
|
156
161
|
if (pmAttrs.listStartOverride != null) counters[level] = pmAttrs.listStartOverride - 1;
|
|
162
|
+
else if (Number.isNaN(counters[level])) counters[level] = (pmAttrs.listLevelStarts?.[level] ?? 1) - 1;
|
|
157
163
|
}
|
|
158
164
|
counters[level] = (counters[level] ?? 0) + 1;
|
|
159
|
-
for (let i = level + 1; i < counters.length; i += 1) counters[i] =
|
|
165
|
+
for (let i = level + 1; i < counters.length; i += 1) counters[i] = NaN;
|
|
160
166
|
const childAdvances = pmAttrs.listImplicitChildLevelAdvances ?? 0;
|
|
161
|
-
if (childAdvances > 0 && level + 1 < counters.length)
|
|
167
|
+
if (childAdvances > 0 && level + 1 < counters.length) {
|
|
168
|
+
const childCounter = counters[level + 1];
|
|
169
|
+
counters[level + 1] = (childCounter === void 0 || Number.isNaN(childCounter) ? 0 : childCounter) + childAdvances;
|
|
170
|
+
}
|
|
162
171
|
listCounters.set(numId, counters);
|
|
163
172
|
if (abstractNumId !== void 0) abstractCounters.set(abstractNumId, [...counters]);
|
|
164
173
|
const levelFormats = pmAttrs.listLevelNumFmts ?? (pmAttrs.listNumFmt ? [pmAttrs.listNumFmt] : void 0);
|
|
@@ -234,7 +243,7 @@ function extractRunFormatting(marks, theme) {
|
|
|
234
243
|
}
|
|
235
244
|
case "characterSpacing": {
|
|
236
245
|
const attrs = expectCharacterSpacingMarkAttrs(mark);
|
|
237
|
-
if (attrs.spacing !== void 0
|
|
246
|
+
if (attrs.spacing !== void 0) formatting.letterSpacing = twipsToPixels(attrs.spacing);
|
|
238
247
|
if (attrs.position !== void 0 && attrs.position !== 0) formatting.positionPx = halfPointsToPixels(attrs.position);
|
|
239
248
|
if (attrs.scale !== void 0 && attrs.scale !== 100) formatting.horizontalScale = attrs.scale;
|
|
240
249
|
if (attrs.kerning !== void 0 && attrs.kerning > 0) formatting.kerningMinPt = halfPointsToPoints(attrs.kerning);
|
|
@@ -336,10 +345,12 @@ function markDefaultBlackTextColorSource(formatting, paraDefaults) {
|
|
|
336
345
|
};
|
|
337
346
|
}
|
|
338
347
|
function mergeRunFormatting(paraDefaults, formatting) {
|
|
339
|
-
|
|
348
|
+
const merged = {
|
|
340
349
|
...paraDefaults,
|
|
341
350
|
...markDefaultBlackTextColorSource(formatting, paraDefaults)
|
|
342
351
|
};
|
|
352
|
+
if (merged.letterSpacing === 0) delete merged.letterSpacing;
|
|
353
|
+
return merged;
|
|
343
354
|
}
|
|
344
355
|
function applyRunFormattingOverrides(formatting, attrs) {
|
|
345
356
|
if (attrs.bold === false) formatting.bold = false;
|
|
@@ -485,9 +496,10 @@ function paragraphToRuns(node, startPos, _options) {
|
|
|
485
496
|
return;
|
|
486
497
|
}
|
|
487
498
|
if (child.type.name === "tab") {
|
|
499
|
+
const formatting = extractRunFormatting(child.marks, theme);
|
|
488
500
|
const run = {
|
|
489
501
|
kind: "tab",
|
|
490
|
-
...mergeRunFormatting(paraDefaults,
|
|
502
|
+
...mergeRunFormatting(paraDefaults, formatting),
|
|
491
503
|
pmStart: childPos,
|
|
492
504
|
pmEnd: childPos + child.nodeSize
|
|
493
505
|
};
|
|
@@ -496,7 +508,8 @@ function paragraphToRuns(node, startPos, _options) {
|
|
|
496
508
|
}
|
|
497
509
|
if (child.type.name === "image") {
|
|
498
510
|
const attrs = expectImageAttrs(child);
|
|
499
|
-
|
|
511
|
+
if (!attrs.src) return;
|
|
512
|
+
const constrained = constrainImageToPage(attrs.width ?? 100, attrs.height ?? 100, _options.pageContentHeight);
|
|
500
513
|
const trackedFmt = extractRunFormatting(child.marks, theme);
|
|
501
514
|
const run = buildImageRun(attrs, constrained, childPos, childPos + child.nodeSize, trackedFmt);
|
|
502
515
|
runs.push(run);
|
|
@@ -613,6 +626,8 @@ function toPreviousListAttrs(previousFormatting) {
|
|
|
613
626
|
if (isNumberFormat(listNumFmt)) attrs.listNumFmt = listNumFmt;
|
|
614
627
|
const listLevelNumFmts = readNumberFormats(previousFormatting["listLevelNumFmts"]);
|
|
615
628
|
if (listLevelNumFmts) attrs.listLevelNumFmts = listLevelNumFmts;
|
|
629
|
+
const listLevelStarts = previousFormatting["listLevelStarts"];
|
|
630
|
+
if (Array.isArray(listLevelStarts) && listLevelStarts.every((value) => typeof value === "number")) attrs.listLevelStarts = listLevelStarts;
|
|
616
631
|
const listAbstractNumId = previousFormatting["listAbstractNumId"];
|
|
617
632
|
if (typeof listAbstractNumId === "number") attrs.listAbstractNumId = listAbstractNumId;
|
|
618
633
|
const listStartOverride = previousFormatting["listStartOverride"];
|
|
@@ -669,9 +684,10 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
|
|
|
669
684
|
if (autoAfter) attrs.spacing.after = 14;
|
|
670
685
|
else if (typeof spaceAfter === "number") attrs.spacing.after = twipsToPixels(spaceAfter);
|
|
671
686
|
const pmSpacingExplicit = pmAttrs.spacingExplicit;
|
|
687
|
+
const spacingFromDocDefaults = pmAttrs.spacingFromDocDefaults;
|
|
672
688
|
const explicit = {};
|
|
673
|
-
if (autoBefore || pmSpacingExplicit?.before) explicit.before = true;
|
|
674
|
-
if (autoAfter || pmSpacingExplicit?.after) explicit.after = true;
|
|
689
|
+
if (autoBefore || pmSpacingExplicit?.before || spacingFromDocDefaults?.before) explicit.before = true;
|
|
690
|
+
if (autoAfter || pmSpacingExplicit?.after || spacingFromDocDefaults?.after) explicit.after = true;
|
|
675
691
|
if (explicit.before !== void 0 || explicit.after !== void 0) attrs.spacingExplicit = explicit;
|
|
676
692
|
if (typeof lineSpacing === "number") if (pmAttrs.lineSpacingRule === "exact" || pmAttrs.lineSpacingRule === "atLeast") {
|
|
677
693
|
attrs.spacing.line = twipsToPixels(lineSpacing);
|
|
@@ -730,6 +746,7 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
|
|
|
730
746
|
return tabStop;
|
|
731
747
|
});
|
|
732
748
|
if (pmAttrs.pageBreakBefore) attrs.pageBreakBefore = true;
|
|
749
|
+
if (pmAttrs.renderedPageBreakBefore) attrs.renderedPageBreakBefore = true;
|
|
733
750
|
if (pmAttrs.keepNext) attrs.keepNext = true;
|
|
734
751
|
if (pmAttrs.keepLines) attrs.keepLines = true;
|
|
735
752
|
if (pmAttrs.widowControl === false) attrs.widowControl = false;
|
|
@@ -809,6 +826,17 @@ function convertParagraph(node, startPos, options) {
|
|
|
809
826
|
const pmAttrs = expectParagraphAttrs(node);
|
|
810
827
|
const runs = paragraphToRuns(node, startPos, options);
|
|
811
828
|
const attrs = convertParagraphAttrs(pmAttrs, options.theme, options.listCounters, options.listAbstractCounters, options.listSeenNumIds, options.defaultTabStopTwips, options.originalListCounters, options.originalListAbstractCounters, options.originalListSeenNumIds);
|
|
829
|
+
const defaultTextFormatting = pmAttrs.defaultTextFormatting;
|
|
830
|
+
if (runs.length === 0) {
|
|
831
|
+
const paragraphMarkFormatting = pmAttrs._originalFormatting?.runProperties;
|
|
832
|
+
if (paragraphMarkFormatting?.fontSize !== void 0) attrs.defaultFontSize = paragraphMarkFormatting.fontSize / 2;
|
|
833
|
+
const paragraphMarkFontFamily = paragraphMarkFormatting?.fontFamily?.ascii ?? paragraphMarkFormatting?.fontFamily?.hAnsi;
|
|
834
|
+
if (paragraphMarkFontFamily) attrs.defaultFontFamily = paragraphMarkFontFamily;
|
|
835
|
+
}
|
|
836
|
+
if (runs.length === 0 && defaultTextFormatting?.hidden === true) {
|
|
837
|
+
attrs.suppressEmptyParagraphHeight = true;
|
|
838
|
+
if (attrs.listMarker !== void 0) attrs.listMarkerHidden = true;
|
|
839
|
+
}
|
|
812
840
|
const bookmarkNames = pmAttrs.bookmarks?.map((b) => b.name);
|
|
813
841
|
return {
|
|
814
842
|
kind: "paragraph",
|
|
@@ -893,6 +921,11 @@ function convertTableCell(node, startPos, options, tableCellMargins) {
|
|
|
893
921
|
} else if (child.type.name === "table") blocks.push(convertTable(child, offset, options));
|
|
894
922
|
offset += child.nodeSize;
|
|
895
923
|
});
|
|
924
|
+
const trailingBlock = blocks.at(-1);
|
|
925
|
+
if (blocks.length > 1 && trailingBlock?.kind === "paragraph" && trailingBlock.runs.every((run) => run.kind === "text" && run.text.length === 0)) trailingBlock.attrs = {
|
|
926
|
+
...trailingBlock.attrs,
|
|
927
|
+
suppressEmptyParagraphHeight: true
|
|
928
|
+
};
|
|
896
929
|
const attrs = expectTableCellAttrs(node);
|
|
897
930
|
const margins = attrs.margins;
|
|
898
931
|
const resolvePaddingSide = (side, cellTwips, tableTwips) => {
|
|
@@ -993,6 +1026,7 @@ function convertTable(node, startPos, options) {
|
|
|
993
1026
|
if (columnWidths) tableBlock.columnWidths = columnWidths;
|
|
994
1027
|
if (width !== void 0) tableBlock.width = width;
|
|
995
1028
|
if (widthType !== void 0) tableBlock.widthType = widthType;
|
|
1029
|
+
if (originalFormatting?.layout !== void 0) tableBlock.layout = originalFormatting.layout;
|
|
996
1030
|
if (justification) tableBlock.justification = justification;
|
|
997
1031
|
if (indentPx !== void 0) tableBlock.indent = indentPx;
|
|
998
1032
|
if (floatingPx) tableBlock.floating = floatingPx;
|
|
@@ -1006,7 +1040,7 @@ function convertImage(node, startPos, pageContentHeight) {
|
|
|
1006
1040
|
const attrs = expectImageAttrs(node);
|
|
1007
1041
|
const wrapType = attrs.wrapType;
|
|
1008
1042
|
const shouldAnchor = wrapType === "behind" || wrapType === "inFront";
|
|
1009
|
-
const constrained = constrainImageToPage(attrs.width
|
|
1043
|
+
const constrained = constrainImageToPage(attrs.width ?? 100, attrs.height ?? 100, pageContentHeight);
|
|
1010
1044
|
const imgBlock = {
|
|
1011
1045
|
kind: "image",
|
|
1012
1046
|
id: nextBlockId(),
|
|
@@ -1075,6 +1109,7 @@ function convertTextBoxNode(node, startPos, opts) {
|
|
|
1075
1109
|
if (attrs.distLeft !== void 0) textBox.distLeft = attrs.distLeft;
|
|
1076
1110
|
if (attrs.distRight !== void 0) textBox.distRight = attrs.distRight;
|
|
1077
1111
|
if (attrs.position !== void 0) textBox.position = attrs.position;
|
|
1112
|
+
if (attrs._docxGroupId !== void 0) setTextBoxGroupId(textBox, attrs._docxGroupId);
|
|
1078
1113
|
return textBox;
|
|
1079
1114
|
}
|
|
1080
1115
|
/**
|
|
@@ -1235,14 +1270,16 @@ function toFlowBlocks(doc, options = {}) {
|
|
|
1235
1270
|
trackedPush(convertTextBoxNode(node, pos, opts));
|
|
1236
1271
|
break;
|
|
1237
1272
|
case "horizontalRule":
|
|
1238
|
-
case "pageBreak":
|
|
1239
|
-
|
|
1273
|
+
case "pageBreak": {
|
|
1274
|
+
const pb = {
|
|
1240
1275
|
kind: "pageBreak",
|
|
1241
1276
|
id: nextBlockId(),
|
|
1242
1277
|
pmStart: pos,
|
|
1243
1278
|
pmEnd: pos + node.nodeSize
|
|
1244
|
-
}
|
|
1279
|
+
};
|
|
1280
|
+
trackedPush(pb);
|
|
1245
1281
|
break;
|
|
1282
|
+
}
|
|
1246
1283
|
default: break;
|
|
1247
1284
|
}
|
|
1248
1285
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "./types.js";
|
|
1
|
+
import { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "./types.js";
|
|
2
2
|
import { PageState, Paginator, PaginatorOptions, createPaginator } from "./paginator.js";
|
|
3
3
|
import { KeepNextChain, calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
|
|
4
4
|
import { BreakDecision, SectionState, applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, scheduleSectionBreak } from "./section-breaks.js";
|
|
@@ -20,9 +20,10 @@ declare function collectSectionConfigs(blocks: FlowBlock[], initialConfig: Secti
|
|
|
20
20
|
/**
|
|
21
21
|
* Apply contextual spacing suppression (OOXML §17.3.1.9).
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Contextual spacing applies independently to each paragraph: suppress the
|
|
24
|
+
* current paragraph's spaceAfter when it opts in, and the next paragraph's
|
|
25
|
+
* spaceBefore when it opts in, provided both paragraphs share a style. Two
|
|
26
|
+
* absent style ids both refer to the document's default paragraph style.
|
|
26
27
|
*
|
|
27
28
|
* This mutates the block attrs in-place before layout runs.
|
|
28
29
|
*/
|
|
@@ -42,4 +43,4 @@ declare function layoutDocument(blocks: FlowBlock[], measures: Measure[], option
|
|
|
42
43
|
*/
|
|
43
44
|
declare function getHeaderRowsHeight(measure: TableMeasure, headerRowCount: number): number;
|
|
44
45
|
//#endregion
|
|
45
|
-
export { BlockId, BorderStyle, type BreakDecision, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, scheduleSectionBreak };
|
|
46
|
+
export { BlockId, BorderStyle, type BreakDecision, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, scheduleSectionBreak, tableColumnsArePinned };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { emuToPixels } from "../utils/units.js";
|
|
1
2
|
import { measuredLineAdvance } from "./lineFlow.js";
|
|
2
|
-
import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "./types.js";
|
|
3
|
+
import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "./types.js";
|
|
3
4
|
import { calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
|
|
4
5
|
import { createPaginator } from "./paginator.js";
|
|
5
6
|
import { getParagraphFragmentPmRange } from "./paragraphFragmentRange.js";
|
|
@@ -51,25 +52,42 @@ function isEmptyParagraph(block) {
|
|
|
51
52
|
if (block.runs.length === 0) return true;
|
|
52
53
|
if (block.runs.length !== 1) return false;
|
|
53
54
|
const r = block.runs[0];
|
|
54
|
-
return r?.kind === "text" &&
|
|
55
|
+
return r?.kind === "text" && r.text === "";
|
|
56
|
+
}
|
|
57
|
+
function isPaginationEmptyParagraph(block) {
|
|
58
|
+
return block.runs.every((run) => {
|
|
59
|
+
if (run.kind === "text") return run.text.trim().length === 0;
|
|
60
|
+
return run.kind === "tab" || run.kind === "lineBreak";
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function pageHasVisibleBodyContent(page, blocksById) {
|
|
64
|
+
for (const fragment of page.fragments) {
|
|
65
|
+
if (fragment.kind !== "paragraph") return true;
|
|
66
|
+
const block = blocksById.get(String(fragment.blockId));
|
|
67
|
+
if (block?.kind !== "paragraph" || !isPaginationEmptyParagraph(block)) return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
55
70
|
}
|
|
56
71
|
/**
|
|
57
72
|
* Get spacing before a paragraph block. Empty paragraphs whose
|
|
58
|
-
* `before`
|
|
59
|
-
*
|
|
73
|
+
* `before` came only from the implicit default paragraph style collapse to
|
|
74
|
+
* zero. An explicit `w:pStyle` selection is authored paragraph formatting,
|
|
75
|
+
* so Word keeps the selected style's spacing even when the paragraph is empty.
|
|
60
76
|
*/
|
|
61
77
|
function getSpacingBefore(block) {
|
|
62
78
|
const value = block.attrs?.spacing?.before ?? 0;
|
|
63
|
-
if (
|
|
79
|
+
if (value === 0) return 0;
|
|
80
|
+
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.spacingExplicit?.before) return 0;
|
|
64
81
|
return value;
|
|
65
82
|
}
|
|
66
83
|
/**
|
|
67
|
-
* Get spacing after a paragraph block. Same
|
|
68
|
-
* as `getSpacingBefore`.
|
|
84
|
+
* Get spacing after a paragraph block. Same implicit-default-style collapse
|
|
85
|
+
* rule as `getSpacingBefore`.
|
|
69
86
|
*/
|
|
70
87
|
function getSpacingAfter(block) {
|
|
71
88
|
const value = block.attrs?.spacing?.after ?? 0;
|
|
72
|
-
if (
|
|
89
|
+
if (value === 0) return 0;
|
|
90
|
+
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.spacingExplicit?.after) return 0;
|
|
73
91
|
return value;
|
|
74
92
|
}
|
|
75
93
|
function hasWidowControl(block) {
|
|
@@ -78,9 +96,10 @@ function hasWidowControl(block) {
|
|
|
78
96
|
/**
|
|
79
97
|
* Apply contextual spacing suppression (OOXML §17.3.1.9).
|
|
80
98
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
99
|
+
* Contextual spacing applies independently to each paragraph: suppress the
|
|
100
|
+
* current paragraph's spaceAfter when it opts in, and the next paragraph's
|
|
101
|
+
* spaceBefore when it opts in, provided both paragraphs share a style. Two
|
|
102
|
+
* absent style ids both refer to the document's default paragraph style.
|
|
84
103
|
*
|
|
85
104
|
* This mutates the block attrs in-place before layout runs.
|
|
86
105
|
*/
|
|
@@ -91,16 +110,15 @@ function applyContextualSpacing(blocks) {
|
|
|
91
110
|
if (curr.kind !== "paragraph" || next.kind !== "paragraph") continue;
|
|
92
111
|
const currAttrs = curr.attrs;
|
|
93
112
|
const nextAttrs = next.attrs;
|
|
94
|
-
if (currAttrs?.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
113
|
+
if (currAttrs?.styleId !== nextAttrs?.styleId) continue;
|
|
114
|
+
if (currAttrs?.contextualSpacing && currAttrs.spacing) currAttrs.spacing = {
|
|
115
|
+
...currAttrs.spacing,
|
|
116
|
+
after: 0
|
|
117
|
+
};
|
|
118
|
+
if (nextAttrs?.contextualSpacing && nextAttrs.spacing) nextAttrs.spacing = {
|
|
119
|
+
...nextAttrs.spacing,
|
|
120
|
+
before: 0
|
|
121
|
+
};
|
|
104
122
|
}
|
|
105
123
|
for (const block of blocks) if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) applyContextualSpacing(cell.blocks);
|
|
106
124
|
else if (block.kind === "textBox") applyContextualSpacing(block.content);
|
|
@@ -145,6 +163,7 @@ function layoutDocument(blocks, measures, options) {
|
|
|
145
163
|
const paginator = createPaginator({
|
|
146
164
|
pageSize: initialConfig.pageSize,
|
|
147
165
|
margins: initialConfig.margins,
|
|
166
|
+
...options.mirrorMargins === true ? { mirrorMargins: true } : {},
|
|
148
167
|
...options.firstPageMargins !== void 0 ? { firstPageMargins: options.firstPageMargins } : {},
|
|
149
168
|
columns: initialConfig.columns ?? DEFAULT_COLUMNS,
|
|
150
169
|
...options.footnoteReservedHeights !== void 0 ? { footnoteReservedHeights: options.footnoteReservedHeights } : {},
|
|
@@ -153,28 +172,36 @@ function layoutDocument(blocks, measures, options) {
|
|
|
153
172
|
applyContextualSpacing(blocks);
|
|
154
173
|
const keepNextChains = computeKeepNextChains(blocks);
|
|
155
174
|
const midChainIndices = getMidChainIndices(keepNextChains);
|
|
175
|
+
const blocksById = /* @__PURE__ */ new Map();
|
|
176
|
+
for (const block of blocks) blocksById.set(String(block.id), block);
|
|
156
177
|
let sectionIdx = 0;
|
|
157
178
|
let activeSectionMarginTop = initialConfig.margins.top;
|
|
158
179
|
let activeSectionPageHeight = initialConfig.pageSize.h;
|
|
159
180
|
let activeSectionMarginBottom = initialConfig.margins.bottom;
|
|
181
|
+
let naturalPageAdvanceSinceRenderedBreak = false;
|
|
160
182
|
for (let i = 0; i < blocks.length; i++) {
|
|
161
183
|
const block = blocks[i];
|
|
162
184
|
const measure = measures[i];
|
|
185
|
+
const hasRenderedPageBreak = block.kind === "paragraph" && block.attrs?.renderedPageBreakBefore === true;
|
|
163
186
|
if (hasPageBreakBefore(block)) paginator.forcePageBreak();
|
|
187
|
+
else if (hasRenderedPageBreak) {
|
|
188
|
+
const state = paginator.getCurrentState();
|
|
189
|
+
if (!(block.kind === "paragraph" && isPaginationEmptyParagraph(block) && naturalPageAdvanceSinceRenderedBreak) && pageHasVisibleBodyContent(state.page, blocksById)) paginator.forcePageBreak();
|
|
190
|
+
}
|
|
191
|
+
if (hasRenderedPageBreak || hasPageBreakBefore(block)) naturalPageAdvanceSinceRenderedBreak = false;
|
|
164
192
|
const chain = keepNextChains.get(i);
|
|
165
193
|
if (chain && !midChainIndices.has(i)) {
|
|
166
194
|
const chainHeight = calculateChainHeight(chain, blocks, measures);
|
|
167
|
-
|
|
168
|
-
const availableHeight = paginator.getAvailableHeight();
|
|
169
|
-
if (chainHeight <= state.contentBottom - state.topMargin && chainHeight > availableHeight && state.page.fragments.length > 0) paginator.forcePageBreak();
|
|
195
|
+
paginator.ensureFits(chainHeight);
|
|
170
196
|
}
|
|
197
|
+
const pageBeforeBlockLayout = paginator.getCurrentState().page.number;
|
|
171
198
|
switch (block.kind) {
|
|
172
199
|
case "paragraph":
|
|
173
200
|
layoutParagraph(block, measure, paginator, paginator.getContentWidth(), options.footnoteHeightById);
|
|
174
201
|
break;
|
|
175
202
|
case "table":
|
|
176
203
|
if (block.floating) layoutFloatingTable(block, measure, paginator, paginator.getContentWidth());
|
|
177
|
-
else layoutTable(block, measure, paginator);
|
|
204
|
+
else layoutTable(block, measure, paginator, options.footnoteHeightById);
|
|
178
205
|
break;
|
|
179
206
|
case "image":
|
|
180
207
|
layoutImage(block, measure, paginator);
|
|
@@ -204,6 +231,9 @@ function layoutDocument(blocks, measures, options) {
|
|
|
204
231
|
}
|
|
205
232
|
default: break;
|
|
206
233
|
}
|
|
234
|
+
const isVisibleBodyBlock = block.kind === "paragraph" ? !isEmptyParagraph(block) : block.kind !== "pageBreak" && block.kind !== "columnBreak" && block.kind !== "sectionBreak";
|
|
235
|
+
if (block.kind === "pageBreak" || block.kind === "columnBreak" || block.kind === "sectionBreak") naturalPageAdvanceSinceRenderedBreak = false;
|
|
236
|
+
else if (isVisibleBodyBlock && paginator.getCurrentState().page.number > pageBeforeBlockLayout) naturalPageAdvanceSinceRenderedBreak = true;
|
|
207
237
|
}
|
|
208
238
|
if (paginator.pages.length === 0) paginator.getCurrentState();
|
|
209
239
|
return {
|
|
@@ -386,9 +416,10 @@ function getHeaderRowsHeight(measure, headerRowCount) {
|
|
|
386
416
|
/**
|
|
387
417
|
* Layout a table block onto pages.
|
|
388
418
|
*/
|
|
389
|
-
function layoutTable(block, measure, paginator) {
|
|
419
|
+
function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
390
420
|
const rows = measure.rows;
|
|
391
421
|
if (rows.length === 0) return;
|
|
422
|
+
const rowFootnoteIds = block.rows.map((row) => collectTableRowFootnoteIds(row, footnoteHeightById));
|
|
392
423
|
const headerRowCount = countHeaderRows(block);
|
|
393
424
|
const headerRowsHeight = getHeaderRowsHeight(measure, headerRowCount);
|
|
394
425
|
let currentRowIndex = 0;
|
|
@@ -397,7 +428,10 @@ function layoutTable(block, measure, paginator) {
|
|
|
397
428
|
let x = paginator.getColumnX(columnIndex);
|
|
398
429
|
if (block.justification === "center") x += (paginator.columnWidth - measure.totalWidth) / 2;
|
|
399
430
|
else if (block.justification === "right") x = x + paginator.columnWidth - measure.totalWidth;
|
|
400
|
-
else if (block.indent
|
|
431
|
+
else if (block.indent !== void 0) {
|
|
432
|
+
const leadingCellMargin = block.rows[0]?.cells[0]?.padding?.left ?? 0;
|
|
433
|
+
x += block.indent - leadingCellMargin;
|
|
434
|
+
}
|
|
401
435
|
return x;
|
|
402
436
|
};
|
|
403
437
|
const getCurrentRowCapacity = (state = paginator.getCurrentState()) => state.rawContentBottom - state.topMargin;
|
|
@@ -474,13 +508,42 @@ function layoutTable(block, measure, paginator) {
|
|
|
474
508
|
const normalHeaderOverhead = repeatHeaderRowsForNormalFragment ? headerRowsHeight : 0;
|
|
475
509
|
let rowsHeight = 0;
|
|
476
510
|
let fittingRows = 0;
|
|
511
|
+
const pageFootnoteIds = new Set(state.page.footnoteIds ?? []);
|
|
512
|
+
const fragmentFootnoteIds = [];
|
|
513
|
+
let fragmentFootnoteHeight = 0;
|
|
514
|
+
let retryOnNextFlowRegion = false;
|
|
477
515
|
for (let j = currentRowIndex; j < rows.length; j++) {
|
|
478
516
|
const rowHeight = rows[j].height;
|
|
479
|
-
|
|
517
|
+
const currentRowFootnoteIds = rowFootnoteIds[j] ?? [];
|
|
518
|
+
const fragmentFootnoteCountBeforeRow = fragmentFootnoteIds.length;
|
|
519
|
+
let rowFootnoteHeight = 0;
|
|
520
|
+
for (const id of currentRowFootnoteIds) {
|
|
521
|
+
if (pageFootnoteIds.has(id) || fragmentFootnoteIds.includes(id)) continue;
|
|
522
|
+
fragmentFootnoteIds.push(id);
|
|
523
|
+
rowFootnoteHeight += footnoteHeightById?.get(id) ?? 0;
|
|
524
|
+
}
|
|
525
|
+
const separatorHeight = pageFootnoteIds.size === 0 && fragmentFootnoteHeight + rowFootnoteHeight > 0 ? 12 : 0;
|
|
526
|
+
if (rowsHeight + rowHeight + normalHeaderOverhead + fragmentFootnoteHeight + rowFootnoteHeight + separatorHeight <= availableHeight) {
|
|
480
527
|
rowsHeight += rowHeight;
|
|
528
|
+
fragmentFootnoteHeight += rowFootnoteHeight;
|
|
481
529
|
fittingRows++;
|
|
482
|
-
} else
|
|
530
|
+
} else if (fittingRows === 0) {
|
|
531
|
+
if (state.cursorY === state.topMargin && state.page.fragments.length === 0) {
|
|
532
|
+
rowsHeight += rowHeight;
|
|
533
|
+
fragmentFootnoteHeight += rowFootnoteHeight;
|
|
534
|
+
fittingRows++;
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
fragmentFootnoteIds.splice(fragmentFootnoteCountBeforeRow);
|
|
538
|
+
paginator.forceColumnBreak();
|
|
539
|
+
retryOnNextFlowRegion = true;
|
|
540
|
+
break;
|
|
541
|
+
} else {
|
|
542
|
+
fragmentFootnoteIds.splice(fragmentFootnoteCountBeforeRow);
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
483
545
|
}
|
|
546
|
+
if (retryOnNextFlowRegion) continue;
|
|
484
547
|
const fragmentHeight = rowsHeight + normalHeaderOverhead;
|
|
485
548
|
const isLastFragment = currentRowIndex + fittingRows >= rows.length;
|
|
486
549
|
const desiredX = computeTableX(state.columnIndex);
|
|
@@ -500,6 +563,7 @@ function layoutTable(block, measure, paginator) {
|
|
|
500
563
|
...repeatHeaderRowsForNormalFragment ? { headerRowCount } : {},
|
|
501
564
|
...block.sdtGroups ? { sdtGroups: block.sdtGroups } : {}
|
|
502
565
|
};
|
|
566
|
+
if (fragmentFootnoteHeight > 0) paginator.addFootnoteHeight(fragmentFootnoteHeight, fragmentFootnoteIds);
|
|
503
567
|
fragment.y = paginator.addFragment(fragment, fragmentHeight, bandSkip, 0).y;
|
|
504
568
|
fragment.x = desiredX;
|
|
505
569
|
currentRowIndex += fittingRows;
|
|
@@ -516,6 +580,25 @@ function layoutTable(block, measure, paginator) {
|
|
|
516
580
|
}
|
|
517
581
|
}
|
|
518
582
|
}
|
|
583
|
+
function collectTableRowFootnoteIds(row, footnoteHeightById) {
|
|
584
|
+
if (!row || !footnoteHeightById) return [];
|
|
585
|
+
const ids = [];
|
|
586
|
+
const walk = (blocks) => {
|
|
587
|
+
for (const block of blocks) {
|
|
588
|
+
if (block.kind === "paragraph") {
|
|
589
|
+
for (const run of block.runs) if (run.kind === "text" && run.footnoteRefId !== void 0 && footnoteHeightById.has(run.footnoteRefId) && !ids.includes(run.footnoteRefId)) ids.push(run.footnoteRefId);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (block.kind === "table") {
|
|
593
|
+
for (const nestedRow of block.rows) for (const cell of nestedRow.cells) walk(cell.blocks);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (block.kind === "textBox") walk(block.content);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
for (const cell of row.cells) walk(cell.blocks);
|
|
600
|
+
return ids;
|
|
601
|
+
}
|
|
519
602
|
/**
|
|
520
603
|
* Layout a floating table (anchored) without advancing the cursor.
|
|
521
604
|
*/
|
|
@@ -531,6 +614,7 @@ function layoutFloatingTable(block, measure, paginator, contentWidth) {
|
|
|
531
614
|
let baseY = margins.top;
|
|
532
615
|
if (floating?.horzAnchor === "page") baseX = 0;
|
|
533
616
|
if (floating?.vertAnchor === "page") baseY = 0;
|
|
617
|
+
else if (floating?.vertAnchor === "text") baseY = state.cursorY;
|
|
534
618
|
let x = paginator.getColumnX(state.columnIndex);
|
|
535
619
|
if (floating?.tblpX !== void 0) x = baseX + floating.tblpX;
|
|
536
620
|
else if (floating?.tblpXSpec) {
|
|
@@ -649,6 +733,35 @@ function layoutTextBox(block, measure, { paginator, sectionMarginTop, sectionPag
|
|
|
649
733
|
state.page.fragments.push(fragment);
|
|
650
734
|
return;
|
|
651
735
|
}
|
|
736
|
+
if (block.position !== void 0) {
|
|
737
|
+
const state = paginator.getCurrentState();
|
|
738
|
+
const horizontal = block.position.horizontal;
|
|
739
|
+
const x = horizontal ? bandFragmentX(horizontal, {
|
|
740
|
+
pageWidth: state.page.size.w,
|
|
741
|
+
marginLeft: state.page.margins.left,
|
|
742
|
+
marginRight: state.page.margins.right,
|
|
743
|
+
boxWidth: measure.width
|
|
744
|
+
}) : paginator.getColumnX(state.columnIndex);
|
|
745
|
+
const vertical = block.position.vertical;
|
|
746
|
+
const y = isPageFrameRelativeAnchor(vertical?.relativeTo) ? state.topMargin + bandTopContentY(vertical, {
|
|
747
|
+
pageHeight: sectionPageHeight,
|
|
748
|
+
marginTop: sectionMarginTop,
|
|
749
|
+
marginBottom: sectionMarginBottom,
|
|
750
|
+
boxHeight: measure.height
|
|
751
|
+
}) : state.cursorY + emuToPixels(vertical?.posOffset ?? 0);
|
|
752
|
+
const fragment = {
|
|
753
|
+
kind: "textBox",
|
|
754
|
+
blockId: block.id,
|
|
755
|
+
x,
|
|
756
|
+
y,
|
|
757
|
+
width: measure.width,
|
|
758
|
+
height: measure.height,
|
|
759
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
760
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
|
|
761
|
+
};
|
|
762
|
+
state.page.fragments.push(fragment);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
652
765
|
const state = paginator.ensureFits(measure.height);
|
|
653
766
|
const fragment = {
|
|
654
767
|
kind: "textBox",
|
|
@@ -715,4 +828,4 @@ function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionTyp
|
|
|
715
828
|
paginator.updateColumns(nextSectionConfig.columns ?? DEFAULT_COLUMNS);
|
|
716
829
|
}
|
|
717
830
|
//#endregion
|
|
718
|
-
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, scheduleSectionBreak };
|
|
831
|
+
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, scheduleSectionBreak, tableColumnsArePinned };
|
|
@@ -2,28 +2,33 @@ import { FlowBlock, Measure } from "./types.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/layout-engine/keep-together.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* A chain of
|
|
5
|
+
* A chain of keepNext-linked paragraphs, including empty separators Word
|
|
6
|
+
* carries through to the next paragraph with visible content.
|
|
6
7
|
*/
|
|
7
8
|
type KeepNextChain = {
|
|
8
|
-
/** Index of the first paragraph in the chain. */startIndex: number; /** Index of the last
|
|
9
|
-
endIndex: number; /** All paragraph indices in the chain. */
|
|
9
|
+
/** Index of the first paragraph in the chain. */startIndex: number; /** Index of the last keepNext or pass-through empty member. */
|
|
10
|
+
endIndex: number; /** All keepNext and pass-through empty paragraph indices in the chain. */
|
|
10
11
|
memberIndices: number[]; /** Index of the anchor paragraph (first non-keepNext after chain), or -1 if none. */
|
|
11
12
|
anchorIndex: number;
|
|
12
13
|
};
|
|
13
14
|
/**
|
|
14
15
|
* Pre-scan blocks to find all keepNext chains.
|
|
15
16
|
*
|
|
16
|
-
* A keepNext chain
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* A keepNext chain starts with a paragraph whose keepNext=true and continues
|
|
18
|
+
* through further keepNext paragraphs and structural empty separators. The
|
|
19
|
+
* first visible non-keepNext paragraph is its anchor.
|
|
19
20
|
*
|
|
20
21
|
* Returns a map from chain start index to chain info.
|
|
21
22
|
*/
|
|
22
23
|
declare function computeKeepNextChains(blocks: FlowBlock[]): Map<number, KeepNextChain>;
|
|
23
24
|
/**
|
|
24
|
-
* Calculate the
|
|
25
|
+
* Calculate the height needed to keep consecutive paragraph boundaries from
|
|
26
|
+
* breaking across pages.
|
|
25
27
|
*
|
|
26
|
-
*
|
|
28
|
+
* Single-line and `keepLines` members are indivisible, so the reservation must
|
|
29
|
+
* continue through them to the anchor's first line. A multi-line member without
|
|
30
|
+
* `keepLines` may itself split; only its first line is needed to satisfy the
|
|
31
|
+
* preceding member's `keepNext`, and the chain can stop there.
|
|
27
32
|
*/
|
|
28
33
|
declare function calculateChainHeight(chain: KeepNextChain, blocks: FlowBlock[], measures: Measure[]): number;
|
|
29
34
|
/**
|