@stll/folio-core 0.3.1 → 0.5.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 +35 -5
- package/dist/ai-edits/headless.js +92 -22
- 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 -115
- package/dist/controller/layoutScheduler.d.ts +2 -1
- package/dist/controller/layoutScheduler.js +6 -0
- package/dist/document-operations.d.ts +122 -0
- package/dist/document-operations.js +575 -0
- package/dist/docx/blockContentParser.js +17 -7
- package/dist/docx/encryption/agileDecryption.d.ts +6 -0
- package/dist/docx/encryption/agileDecryption.js +209 -0
- package/dist/docx/encryption/compoundFile.d.ts +20 -0
- package/dist/docx/encryption/compoundFile.js +251 -0
- package/dist/docx/encryption/containerFormat.d.ts +15 -0
- package/dist/docx/encryption/containerFormat.js +40 -0
- package/dist/docx/encryption/cryptoBytes.d.ts +11 -0
- package/dist/docx/encryption/cryptoBytes.js +55 -0
- package/dist/docx/encryption/encryptionInfo.d.ts +32 -0
- package/dist/docx/encryption/encryptionInfo.js +128 -0
- package/dist/docx/encryption/errors.d.ts +18 -0
- package/dist/docx/encryption/errors.js +13 -0
- package/dist/docx/encryption/index.d.ts +4 -0
- package/dist/docx/encryption/index.js +4 -0
- package/dist/docx/encryption/openEncryptedDocx.d.ts +28 -0
- package/dist/docx/encryption/openEncryptedDocx.js +65 -0
- 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/index.d.ts +3 -1
- package/dist/docx/index.js +3 -1
- 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/parser.d.ts +4 -5
- package/dist/docx/parser.js +8 -2
- 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/unzip.d.ts +7 -5
- package/dist/docx/unzip.js +8 -2
- package/dist/docx/xmlParser.js +11 -4
- 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 +6 -8
- package/dist/layout-bridge/convert/headerFooterLayout.js +21 -13
- package/dist/layout-bridge/convert/toFlowBlocks.js +58 -19
- package/dist/layout-engine/index.d.ts +6 -5
- package/dist/layout-engine/index.js +116 -27
- 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 +43 -19
- package/dist/layout-engine/measure/measureParagraph.js +91 -14
- 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 +33 -9
- package/dist/layout-engine/types.js +20 -2
- package/dist/layout-painter/renderPage.d.ts +15 -1
- package/dist/layout-painter/renderPage.js +29 -12
- package/dist/layout-painter/renderParagraph.js +44 -9
- package/dist/layout-painter/renderTable.js +27 -7
- package/dist/managers/DocumentLoaderManager.d.ts +3 -1
- package/dist/managers/DocumentLoaderManager.js +13 -5
- package/dist/managers/TableSelectionManager.js +1 -1
- 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 +3 -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/index.js +1 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +68 -0
- package/dist/prosemirror/commands/propertyChangeScope.js +242 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +21 -5
- package/dist/prosemirror/conversion/toProseDoc.js +43 -15
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +3 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +6 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +12 -11
- package/dist/prosemirror/findReplaceSelection.js +2 -1
- package/dist/prosemirror/index.js +1 -1
- package/dist/prosemirror/insertOperations.js +1 -1
- package/dist/prosemirror/schema/nodes.d.ts +14 -7
- 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 +65 -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 +7 -3
- package/dist/paged-layout/headerFooterMargins.d.ts +0 -73
- package/dist/paged-layout/headerFooterMargins.js +0 -120
|
@@ -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,8 +746,10 @@ 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;
|
|
752
|
+
if (pmAttrs.widowControl === false) attrs.widowControl = false;
|
|
735
753
|
if (pmAttrs.contextualSpacing) attrs.contextualSpacing = true;
|
|
736
754
|
if (pmAttrs.runInWithNext) attrs.runInWithNext = true;
|
|
737
755
|
if (directionIsRtl(pmAttrs.direction)) attrs.bidi = true;
|
|
@@ -808,6 +826,17 @@ function convertParagraph(node, startPos, options) {
|
|
|
808
826
|
const pmAttrs = expectParagraphAttrs(node);
|
|
809
827
|
const runs = paragraphToRuns(node, startPos, options);
|
|
810
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
|
+
}
|
|
811
840
|
const bookmarkNames = pmAttrs.bookmarks?.map((b) => b.name);
|
|
812
841
|
return {
|
|
813
842
|
kind: "paragraph",
|
|
@@ -892,6 +921,11 @@ function convertTableCell(node, startPos, options, tableCellMargins) {
|
|
|
892
921
|
} else if (child.type.name === "table") blocks.push(convertTable(child, offset, options));
|
|
893
922
|
offset += child.nodeSize;
|
|
894
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
|
+
};
|
|
895
929
|
const attrs = expectTableCellAttrs(node);
|
|
896
930
|
const margins = attrs.margins;
|
|
897
931
|
const resolvePaddingSide = (side, cellTwips, tableTwips) => {
|
|
@@ -941,6 +975,7 @@ function convertTableRow(node, startPos, options, tableCellMargins) {
|
|
|
941
975
|
if (attrs.height) row.height = twipsToPixels(attrs.height);
|
|
942
976
|
if (attrs.heightRule) row.heightRule = attrs.heightRule;
|
|
943
977
|
if (attrs.isHeader) row.isHeader = attrs.isHeader;
|
|
978
|
+
if (attrs.hidden) row.hidden = attrs.hidden;
|
|
944
979
|
return row;
|
|
945
980
|
}
|
|
946
981
|
/**
|
|
@@ -991,6 +1026,7 @@ function convertTable(node, startPos, options) {
|
|
|
991
1026
|
if (columnWidths) tableBlock.columnWidths = columnWidths;
|
|
992
1027
|
if (width !== void 0) tableBlock.width = width;
|
|
993
1028
|
if (widthType !== void 0) tableBlock.widthType = widthType;
|
|
1029
|
+
if (originalFormatting?.layout !== void 0) tableBlock.layout = originalFormatting.layout;
|
|
994
1030
|
if (justification) tableBlock.justification = justification;
|
|
995
1031
|
if (indentPx !== void 0) tableBlock.indent = indentPx;
|
|
996
1032
|
if (floatingPx) tableBlock.floating = floatingPx;
|
|
@@ -1004,7 +1040,7 @@ function convertImage(node, startPos, pageContentHeight) {
|
|
|
1004
1040
|
const attrs = expectImageAttrs(node);
|
|
1005
1041
|
const wrapType = attrs.wrapType;
|
|
1006
1042
|
const shouldAnchor = wrapType === "behind" || wrapType === "inFront";
|
|
1007
|
-
const constrained = constrainImageToPage(attrs.width
|
|
1043
|
+
const constrained = constrainImageToPage(attrs.width ?? 100, attrs.height ?? 100, pageContentHeight);
|
|
1008
1044
|
const imgBlock = {
|
|
1009
1045
|
kind: "image",
|
|
1010
1046
|
id: nextBlockId(),
|
|
@@ -1073,6 +1109,7 @@ function convertTextBoxNode(node, startPos, opts) {
|
|
|
1073
1109
|
if (attrs.distLeft !== void 0) textBox.distLeft = attrs.distLeft;
|
|
1074
1110
|
if (attrs.distRight !== void 0) textBox.distRight = attrs.distRight;
|
|
1075
1111
|
if (attrs.position !== void 0) textBox.position = attrs.position;
|
|
1112
|
+
if (attrs._docxGroupId !== void 0) setTextBoxGroupId(textBox, attrs._docxGroupId);
|
|
1076
1113
|
return textBox;
|
|
1077
1114
|
}
|
|
1078
1115
|
/**
|
|
@@ -1233,14 +1270,16 @@ function toFlowBlocks(doc, options = {}) {
|
|
|
1233
1270
|
trackedPush(convertTextBoxNode(node, pos, opts));
|
|
1234
1271
|
break;
|
|
1235
1272
|
case "horizontalRule":
|
|
1236
|
-
case "pageBreak":
|
|
1237
|
-
|
|
1273
|
+
case "pageBreak": {
|
|
1274
|
+
const pb = {
|
|
1238
1275
|
kind: "pageBreak",
|
|
1239
1276
|
id: nextBlockId(),
|
|
1240
1277
|
pmStart: pos,
|
|
1241
1278
|
pmEnd: pos + node.nodeSize
|
|
1242
|
-
}
|
|
1279
|
+
};
|
|
1280
|
+
trackedPush(pb);
|
|
1243
1281
|
break;
|
|
1282
|
+
}
|
|
1244
1283
|
default: break;
|
|
1245
1284
|
}
|
|
1246
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,33 +52,54 @@ 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
|
}
|
|
93
|
+
function hasWidowControl(block) {
|
|
94
|
+
return block.attrs?.widowControl !== false;
|
|
95
|
+
}
|
|
75
96
|
/**
|
|
76
97
|
* Apply contextual spacing suppression (OOXML §17.3.1.9).
|
|
77
98
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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.
|
|
81
103
|
*
|
|
82
104
|
* This mutates the block attrs in-place before layout runs.
|
|
83
105
|
*/
|
|
@@ -88,16 +110,15 @@ function applyContextualSpacing(blocks) {
|
|
|
88
110
|
if (curr.kind !== "paragraph" || next.kind !== "paragraph") continue;
|
|
89
111
|
const currAttrs = curr.attrs;
|
|
90
112
|
const nextAttrs = next.attrs;
|
|
91
|
-
if (currAttrs?.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
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
|
+
};
|
|
101
122
|
}
|
|
102
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);
|
|
103
124
|
else if (block.kind === "textBox") applyContextualSpacing(block.content);
|
|
@@ -142,6 +163,7 @@ function layoutDocument(blocks, measures, options) {
|
|
|
142
163
|
const paginator = createPaginator({
|
|
143
164
|
pageSize: initialConfig.pageSize,
|
|
144
165
|
margins: initialConfig.margins,
|
|
166
|
+
...options.mirrorMargins === true ? { mirrorMargins: true } : {},
|
|
145
167
|
...options.firstPageMargins !== void 0 ? { firstPageMargins: options.firstPageMargins } : {},
|
|
146
168
|
columns: initialConfig.columns ?? DEFAULT_COLUMNS,
|
|
147
169
|
...options.footnoteReservedHeights !== void 0 ? { footnoteReservedHeights: options.footnoteReservedHeights } : {},
|
|
@@ -150,21 +172,29 @@ function layoutDocument(blocks, measures, options) {
|
|
|
150
172
|
applyContextualSpacing(blocks);
|
|
151
173
|
const keepNextChains = computeKeepNextChains(blocks);
|
|
152
174
|
const midChainIndices = getMidChainIndices(keepNextChains);
|
|
175
|
+
const blocksById = /* @__PURE__ */ new Map();
|
|
176
|
+
for (const block of blocks) blocksById.set(String(block.id), block);
|
|
153
177
|
let sectionIdx = 0;
|
|
154
178
|
let activeSectionMarginTop = initialConfig.margins.top;
|
|
155
179
|
let activeSectionPageHeight = initialConfig.pageSize.h;
|
|
156
180
|
let activeSectionMarginBottom = initialConfig.margins.bottom;
|
|
181
|
+
let naturalPageAdvanceSinceRenderedBreak = false;
|
|
157
182
|
for (let i = 0; i < blocks.length; i++) {
|
|
158
183
|
const block = blocks[i];
|
|
159
184
|
const measure = measures[i];
|
|
185
|
+
const hasRenderedPageBreak = block.kind === "paragraph" && block.attrs?.renderedPageBreakBefore === true;
|
|
160
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;
|
|
161
192
|
const chain = keepNextChains.get(i);
|
|
162
193
|
if (chain && !midChainIndices.has(i)) {
|
|
163
194
|
const chainHeight = calculateChainHeight(chain, blocks, measures);
|
|
164
|
-
|
|
165
|
-
const availableHeight = paginator.getAvailableHeight();
|
|
166
|
-
if (chainHeight <= state.contentBottom - state.topMargin && chainHeight > availableHeight && state.page.fragments.length > 0) paginator.forcePageBreak();
|
|
195
|
+
paginator.ensureFits(chainHeight);
|
|
167
196
|
}
|
|
197
|
+
const pageBeforeBlockLayout = paginator.getCurrentState().page.number;
|
|
168
198
|
switch (block.kind) {
|
|
169
199
|
case "paragraph":
|
|
170
200
|
layoutParagraph(block, measure, paginator, paginator.getContentWidth(), options.footnoteHeightById);
|
|
@@ -201,6 +231,9 @@ function layoutDocument(blocks, measures, options) {
|
|
|
201
231
|
}
|
|
202
232
|
default: break;
|
|
203
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;
|
|
204
237
|
}
|
|
205
238
|
if (paginator.pages.length === 0) paginator.getCurrentState();
|
|
206
239
|
return {
|
|
@@ -309,6 +342,28 @@ function layoutParagraph(block, measure, paginator, contentWidth, footnoteHeight
|
|
|
309
342
|
fittingLines++;
|
|
310
343
|
} else break;
|
|
311
344
|
}
|
|
345
|
+
let forceBreakAfterFragment = false;
|
|
346
|
+
if (hasWidowControl(block)) {
|
|
347
|
+
const remainingAfter = lines.length - (currentLineIndex + fittingLines);
|
|
348
|
+
if (fittingLines > 1 && remainingAfter === 1) {
|
|
349
|
+
if (currentLineIndex === 0 && fittingLines === 2 && state.cursorY !== state.topMargin) {
|
|
350
|
+
paginator.forceColumnBreak();
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
fittingLines -= 1;
|
|
354
|
+
forceBreakAfterFragment = true;
|
|
355
|
+
linesHeight = 0;
|
|
356
|
+
linesFnHeight = 0;
|
|
357
|
+
linesFnIds.length = 0;
|
|
358
|
+
for (let j = currentLineIndex; j < currentLineIndex + fittingLines; j++) {
|
|
359
|
+
const line = lines[j];
|
|
360
|
+
linesHeight += measuredLineAdvance(line);
|
|
361
|
+
const lineRefs = getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById);
|
|
362
|
+
linesFnHeight += lineRefs.height;
|
|
363
|
+
for (const id of lineRefs.ids) linesFnIds.push(id);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
312
367
|
const isFirstFragment = currentLineIndex === 0;
|
|
313
368
|
const isLastFragment = currentLineIndex + fittingLines >= lines.length;
|
|
314
369
|
const effectiveSpaceBefore = isFirstFragment ? spaceBefore : 0;
|
|
@@ -336,7 +391,8 @@ function layoutParagraph(block, measure, paginator, contentWidth, footnoteHeight
|
|
|
336
391
|
fragment.y = paginator.addFragment(fragment, linesHeight, effectiveSpaceBefore, effectiveSpaceAfter).y;
|
|
337
392
|
if (linesFnHeight > 0) paginator.addFootnoteHeight(linesFnHeight, linesFnIds);
|
|
338
393
|
currentLineIndex += fittingLines;
|
|
339
|
-
if (currentLineIndex < lines.length) paginator.
|
|
394
|
+
if (currentLineIndex < lines.length) if (forceBreakAfterFragment) paginator.forceColumnBreak();
|
|
395
|
+
else paginator.ensureFits(measuredLineAdvance(lines[currentLineIndex]));
|
|
340
396
|
}
|
|
341
397
|
}
|
|
342
398
|
/**
|
|
@@ -371,7 +427,10 @@ function layoutTable(block, measure, paginator) {
|
|
|
371
427
|
let x = paginator.getColumnX(columnIndex);
|
|
372
428
|
if (block.justification === "center") x += (paginator.columnWidth - measure.totalWidth) / 2;
|
|
373
429
|
else if (block.justification === "right") x = x + paginator.columnWidth - measure.totalWidth;
|
|
374
|
-
else if (block.indent
|
|
430
|
+
else if (block.indent !== void 0) {
|
|
431
|
+
const leadingCellMargin = block.rows[0]?.cells[0]?.padding?.left ?? 0;
|
|
432
|
+
x += block.indent - leadingCellMargin;
|
|
433
|
+
}
|
|
375
434
|
return x;
|
|
376
435
|
};
|
|
377
436
|
const getCurrentRowCapacity = (state = paginator.getCurrentState()) => state.rawContentBottom - state.topMargin;
|
|
@@ -505,6 +564,7 @@ function layoutFloatingTable(block, measure, paginator, contentWidth) {
|
|
|
505
564
|
let baseY = margins.top;
|
|
506
565
|
if (floating?.horzAnchor === "page") baseX = 0;
|
|
507
566
|
if (floating?.vertAnchor === "page") baseY = 0;
|
|
567
|
+
else if (floating?.vertAnchor === "text") baseY = state.cursorY;
|
|
508
568
|
let x = paginator.getColumnX(state.columnIndex);
|
|
509
569
|
if (floating?.tblpX !== void 0) x = baseX + floating.tblpX;
|
|
510
570
|
else if (floating?.tblpXSpec) {
|
|
@@ -623,6 +683,35 @@ function layoutTextBox(block, measure, { paginator, sectionMarginTop, sectionPag
|
|
|
623
683
|
state.page.fragments.push(fragment);
|
|
624
684
|
return;
|
|
625
685
|
}
|
|
686
|
+
if (block.position !== void 0) {
|
|
687
|
+
const state = paginator.getCurrentState();
|
|
688
|
+
const horizontal = block.position.horizontal;
|
|
689
|
+
const x = horizontal ? bandFragmentX(horizontal, {
|
|
690
|
+
pageWidth: state.page.size.w,
|
|
691
|
+
marginLeft: state.page.margins.left,
|
|
692
|
+
marginRight: state.page.margins.right,
|
|
693
|
+
boxWidth: measure.width
|
|
694
|
+
}) : paginator.getColumnX(state.columnIndex);
|
|
695
|
+
const vertical = block.position.vertical;
|
|
696
|
+
const y = isPageFrameRelativeAnchor(vertical?.relativeTo) ? state.topMargin + bandTopContentY(vertical, {
|
|
697
|
+
pageHeight: sectionPageHeight,
|
|
698
|
+
marginTop: sectionMarginTop,
|
|
699
|
+
marginBottom: sectionMarginBottom,
|
|
700
|
+
boxHeight: measure.height
|
|
701
|
+
}) : state.cursorY + emuToPixels(vertical?.posOffset ?? 0);
|
|
702
|
+
const fragment = {
|
|
703
|
+
kind: "textBox",
|
|
704
|
+
blockId: block.id,
|
|
705
|
+
x,
|
|
706
|
+
y,
|
|
707
|
+
width: measure.width,
|
|
708
|
+
height: measure.height,
|
|
709
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
710
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
|
|
711
|
+
};
|
|
712
|
+
state.page.fragments.push(fragment);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
626
715
|
const state = paginator.ensureFits(measure.height);
|
|
627
716
|
const fragment = {
|
|
628
717
|
kind: "textBox",
|
|
@@ -689,4 +778,4 @@ function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionTyp
|
|
|
689
778
|
paginator.updateColumns(nextSectionConfig.columns ?? DEFAULT_COLUMNS);
|
|
690
779
|
}
|
|
691
780
|
//#endregion
|
|
692
|
-
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 };
|
|
781
|
+
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
|
/**
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
//#region src/layout-engine/keep-together.ts
|
|
2
|
+
function isVisuallyEmptyParagraph(block) {
|
|
3
|
+
if (block.runs.length === 0) return true;
|
|
4
|
+
if (block.runs.length !== 1) return false;
|
|
5
|
+
const run = block.runs.at(0);
|
|
6
|
+
return run?.kind === "text" && run.text === "";
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
9
|
* Pre-scan blocks to find all keepNext chains.
|
|
4
10
|
*
|
|
5
|
-
* A keepNext chain
|
|
6
|
-
*
|
|
7
|
-
*
|
|
11
|
+
* A keepNext chain starts with a paragraph whose keepNext=true and continues
|
|
12
|
+
* through further keepNext paragraphs and structural empty separators. The
|
|
13
|
+
* first visible non-keepNext paragraph is its anchor.
|
|
8
14
|
*
|
|
9
15
|
* Returns a map from chain start index to chain info.
|
|
10
16
|
*/
|
|
@@ -22,7 +28,8 @@ function computeKeepNextChains(blocks) {
|
|
|
22
28
|
const nextBlock = blocks[j];
|
|
23
29
|
if (nextBlock.kind === "sectionBreak" || nextBlock.kind === "pageBreak" || nextBlock.kind === "columnBreak") break;
|
|
24
30
|
if (nextBlock.kind !== "paragraph") break;
|
|
25
|
-
|
|
31
|
+
const nextPara = nextBlock;
|
|
32
|
+
if (nextPara.attrs?.keepNext || isVisuallyEmptyParagraph(nextPara)) {
|
|
26
33
|
memberIndices.push(j);
|
|
27
34
|
endIndex = j;
|
|
28
35
|
processed.add(j);
|
|
@@ -44,30 +51,38 @@ function computeKeepNextChains(blocks) {
|
|
|
44
51
|
return chains;
|
|
45
52
|
}
|
|
46
53
|
/**
|
|
47
|
-
* Calculate the
|
|
54
|
+
* Calculate the height needed to keep consecutive paragraph boundaries from
|
|
55
|
+
* breaking across pages.
|
|
48
56
|
*
|
|
49
|
-
*
|
|
57
|
+
* Single-line and `keepLines` members are indivisible, so the reservation must
|
|
58
|
+
* continue through them to the anchor's first line. A multi-line member without
|
|
59
|
+
* `keepLines` may itself split; only its first line is needed to satisfy the
|
|
60
|
+
* preceding member's `keepNext`, and the chain can stop there.
|
|
50
61
|
*/
|
|
51
62
|
function calculateChainHeight(chain, blocks, measures) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
const firstMemberIndex = chain.memberIndices.at(0);
|
|
64
|
+
if (firstMemberIndex === void 0) return 0;
|
|
65
|
+
const firstBlock = blocks[firstMemberIndex];
|
|
66
|
+
const firstMeasure = measures[firstMemberIndex];
|
|
67
|
+
if (firstBlock?.kind !== "paragraph" || firstMeasure?.kind !== "paragraph") return 0;
|
|
68
|
+
let totalHeight = (firstBlock.attrs?.spacing?.before ?? 0) + firstMeasure.totalHeight;
|
|
69
|
+
let trailingSpacing = firstBlock.attrs?.spacing?.after ?? 0;
|
|
70
|
+
const successorIndices = [...chain.memberIndices.slice(1)];
|
|
71
|
+
if (chain.anchorIndex !== -1) successorIndices.push(chain.anchorIndex);
|
|
72
|
+
for (let index = 0; index < successorIndices.length; index++) {
|
|
73
|
+
const successorIndex = successorIndices[index];
|
|
74
|
+
if (successorIndex === void 0) continue;
|
|
75
|
+
const successorBlock = blocks[successorIndex];
|
|
76
|
+
const successorMeasure = measures[successorIndex];
|
|
77
|
+
if (successorBlock?.kind !== "paragraph" || successorMeasure?.kind !== "paragraph") return totalHeight;
|
|
78
|
+
totalHeight += Math.max(trailingSpacing, successorBlock.attrs?.spacing?.before ?? 0);
|
|
79
|
+
const firstLine = successorMeasure.lines.at(0);
|
|
80
|
+
if (!firstLine) return totalHeight;
|
|
81
|
+
const isAnchor = index === successorIndices.length - 1 && chain.anchorIndex !== -1;
|
|
82
|
+
const isSplittable = successorMeasure.lines.length > 1 && !successorBlock.attrs?.keepLines;
|
|
83
|
+
if (isAnchor || isSplittable) return totalHeight + firstLine.lineHeight;
|
|
84
|
+
totalHeight += successorMeasure.totalHeight;
|
|
85
|
+
trailingSpacing = successorBlock.attrs?.spacing?.after ?? 0;
|
|
71
86
|
}
|
|
72
87
|
return totalHeight;
|
|
73
88
|
}
|