@stll/folio-core 0.4.0 → 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 +33 -4
- package/dist/ai-edits/headless.js +83 -15
- 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 +122 -0
- package/dist/document-operations.js +575 -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/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 +89 -26
- 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 +28 -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/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 +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/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 +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/index.js +1 -1
- package/dist/prosemirror/insertOperations.js +1 -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
|
@@ -16,20 +16,149 @@ const FOOTNOTE_FONT_SIZE = 8;
|
|
|
16
16
|
* area.
|
|
17
17
|
*/
|
|
18
18
|
function collectFootnoteRefs(blocks) {
|
|
19
|
+
return collectNoteRefs(blocks, "footnoteRefId").map(({ noteId, pmPos }) => ({
|
|
20
|
+
footnoteId: noteId,
|
|
21
|
+
pmPos
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Scan FlowBlocks for runs with endnoteRefId set, in document order.
|
|
26
|
+
* Same recursive walk as `collectFootnoteRefs`.
|
|
27
|
+
*/
|
|
28
|
+
function collectEndnoteRefs(blocks) {
|
|
29
|
+
return collectNoteRefs(blocks, "endnoteRefId").map(({ noteId, pmPos }) => ({
|
|
30
|
+
endnoteId: noteId,
|
|
31
|
+
pmPos
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
function collectNoteRefs(blocks, idKey) {
|
|
19
35
|
const refs = [];
|
|
20
36
|
const walk = (containerBlocks) => {
|
|
21
|
-
for (const block of containerBlocks) if (block.kind === "paragraph") {
|
|
22
|
-
|
|
23
|
-
|
|
37
|
+
for (const block of containerBlocks) if (block.kind === "paragraph") for (const run of block.runs) {
|
|
38
|
+
if (run.kind !== "text") continue;
|
|
39
|
+
const noteId = run[idKey];
|
|
40
|
+
if (noteId !== void 0) refs.push({
|
|
41
|
+
noteId,
|
|
24
42
|
pmPos: run.pmStart ?? 0
|
|
25
43
|
});
|
|
26
|
-
}
|
|
44
|
+
}
|
|
45
|
+
else if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) walk(cell.blocks);
|
|
27
46
|
else if (block.kind === "textBox") walk(block.content);
|
|
28
47
|
};
|
|
29
48
|
walk(blocks);
|
|
30
49
|
return refs;
|
|
31
50
|
}
|
|
32
51
|
/**
|
|
52
|
+
* Assign sequential display numbers (1, 2, 3, …) to notes in order of first
|
|
53
|
+
* reference. Word numbers footnotes/endnotes by reference order, not by their
|
|
54
|
+
* `w:id` values, which may be non-contiguous or out of document order. Only
|
|
55
|
+
* `normal` notes are numbered; separator/continuation notes and refs to
|
|
56
|
+
* missing notes consume no number.
|
|
57
|
+
*/
|
|
58
|
+
function computeNoteDisplayNumbers(notes, refNoteIds) {
|
|
59
|
+
const numberable = /* @__PURE__ */ new Set();
|
|
60
|
+
for (const note of notes) if (note.noteType === "normal") numberable.add(note.id);
|
|
61
|
+
const displayNumbers = /* @__PURE__ */ new Map();
|
|
62
|
+
let nextNumber = 1;
|
|
63
|
+
for (const noteId of refNoteIds) {
|
|
64
|
+
if (displayNumbers.has(noteId) || !numberable.has(noteId)) continue;
|
|
65
|
+
displayNumbers.set(noteId, nextNumber);
|
|
66
|
+
nextNumber++;
|
|
67
|
+
}
|
|
68
|
+
return displayNumbers;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Rewrite body reference-marker run text from the raw `w:id` (which the PM
|
|
72
|
+
* doc stores as the marker text; the save path serializes from the mark
|
|
73
|
+
* attrs, never from this text) to the sequential display number, so the
|
|
74
|
+
* inline marker matches the number rendered in the note area. Must run
|
|
75
|
+
* before measurement so marker widths match the painted digits. Untouched
|
|
76
|
+
* blocks/runs are returned by reference so the painter's fingerprinting and
|
|
77
|
+
* the incremental measure path see them unchanged. Runs keep their PM range,
|
|
78
|
+
* so click-to-position still resolves into the marker (same contract as the
|
|
79
|
+
* template preview substitution).
|
|
80
|
+
*/
|
|
81
|
+
function remapNoteMarkerText(blocks, maps) {
|
|
82
|
+
if ((maps.footnoteNumbers?.size ?? 0) === 0 && (maps.endnoteNumbers?.size ?? 0) === 0) return blocks;
|
|
83
|
+
let changed = false;
|
|
84
|
+
const next = blocks.map((block) => {
|
|
85
|
+
const remapped = remapNoteMarkerBlock(block, maps);
|
|
86
|
+
changed ||= remapped !== block;
|
|
87
|
+
return remapped;
|
|
88
|
+
});
|
|
89
|
+
return changed ? next : blocks;
|
|
90
|
+
}
|
|
91
|
+
function remapNoteMarkerBlock(block, maps) {
|
|
92
|
+
if (block.kind === "paragraph") return remapNoteMarkerParagraph(block, maps);
|
|
93
|
+
if (block.kind === "table") {
|
|
94
|
+
let tableChanged = false;
|
|
95
|
+
const rows = block.rows.map((row) => {
|
|
96
|
+
let rowChanged = false;
|
|
97
|
+
const cells = row.cells.map((cell) => {
|
|
98
|
+
let cellChanged = false;
|
|
99
|
+
const cellBlocks = cell.blocks.map((cellBlock) => {
|
|
100
|
+
const remapped = remapNoteMarkerBlock(cellBlock, maps);
|
|
101
|
+
cellChanged ||= remapped !== cellBlock;
|
|
102
|
+
return remapped;
|
|
103
|
+
});
|
|
104
|
+
return cellChanged ? {
|
|
105
|
+
...cell,
|
|
106
|
+
blocks: cellBlocks
|
|
107
|
+
} : cell;
|
|
108
|
+
});
|
|
109
|
+
rowChanged = cells.some((cell, index) => cell !== row.cells[index]);
|
|
110
|
+
tableChanged ||= rowChanged;
|
|
111
|
+
return rowChanged ? {
|
|
112
|
+
...row,
|
|
113
|
+
cells
|
|
114
|
+
} : row;
|
|
115
|
+
});
|
|
116
|
+
return tableChanged ? {
|
|
117
|
+
...block,
|
|
118
|
+
rows
|
|
119
|
+
} : block;
|
|
120
|
+
}
|
|
121
|
+
if (block.kind === "textBox") {
|
|
122
|
+
let boxChanged = false;
|
|
123
|
+
const content = block.content.map((paragraph) => {
|
|
124
|
+
const remapped = remapNoteMarkerParagraph(paragraph, maps);
|
|
125
|
+
boxChanged ||= remapped !== paragraph;
|
|
126
|
+
return remapped;
|
|
127
|
+
});
|
|
128
|
+
return boxChanged ? {
|
|
129
|
+
...block,
|
|
130
|
+
content
|
|
131
|
+
} : block;
|
|
132
|
+
}
|
|
133
|
+
return block;
|
|
134
|
+
}
|
|
135
|
+
function remapNoteMarkerParagraph(block, maps) {
|
|
136
|
+
let changed = false;
|
|
137
|
+
const runs = block.runs.map((run) => {
|
|
138
|
+
const remapped = remapNoteMarkerRun(run, maps);
|
|
139
|
+
changed ||= remapped !== run;
|
|
140
|
+
return remapped;
|
|
141
|
+
});
|
|
142
|
+
return changed ? {
|
|
143
|
+
...block,
|
|
144
|
+
runs
|
|
145
|
+
} : block;
|
|
146
|
+
}
|
|
147
|
+
function remapNoteMarkerRun(run, maps) {
|
|
148
|
+
if (run.kind !== "text") return run;
|
|
149
|
+
const displayNumber = getRunDisplayNumber(run, maps);
|
|
150
|
+
if (displayNumber === void 0) return run;
|
|
151
|
+
const text = String(displayNumber);
|
|
152
|
+
return run.text === text ? run : {
|
|
153
|
+
...run,
|
|
154
|
+
text
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function getRunDisplayNumber(run, maps) {
|
|
158
|
+
if (run.footnoteRefId !== void 0) return maps.footnoteNumbers?.get(run.footnoteRefId);
|
|
159
|
+
if (run.endnoteRefId !== void 0) return maps.endnoteNumbers?.get(run.endnoteRefId);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
33
162
|
* After layout, determine which footnotes appear on which pages.
|
|
34
163
|
* Checks each page's fragments to see if any footnoteRef PM positions fall within.
|
|
35
164
|
*
|
|
@@ -279,16 +408,11 @@ function buildFootnoteContentMap(footnotes, footnoteRefs, contentWidth, options
|
|
|
279
408
|
const contentMap = /* @__PURE__ */ new Map();
|
|
280
409
|
const footnoteById = /* @__PURE__ */ new Map();
|
|
281
410
|
for (const fn of footnotes) if (fn.noteType === "normal") footnoteById.set(fn.id, fn);
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
if (seen.has(ref.footnoteId)) continue;
|
|
286
|
-
seen.add(ref.footnoteId);
|
|
287
|
-
const footnote = footnoteById.get(ref.footnoteId);
|
|
411
|
+
const displayNumbers = computeNoteDisplayNumbers(footnotes, footnoteRefs.map((ref) => ref.footnoteId));
|
|
412
|
+
for (const [footnoteId, displayNumber] of displayNumbers) {
|
|
413
|
+
const footnote = footnoteById.get(footnoteId);
|
|
288
414
|
if (!footnote) continue;
|
|
289
|
-
|
|
290
|
-
contentMap.set(ref.footnoteId, content);
|
|
291
|
-
displayNumber++;
|
|
415
|
+
contentMap.set(footnoteId, convertFootnoteToContent(footnote, displayNumber, contentWidth, options));
|
|
292
416
|
}
|
|
293
417
|
return contentMap;
|
|
294
418
|
}
|
|
@@ -313,4 +437,4 @@ function calculateFootnoteReservedHeights(pageFootnoteMap, footnoteContentMap) {
|
|
|
313
437
|
return reserved;
|
|
314
438
|
}
|
|
315
439
|
//#endregion
|
|
316
|
-
export { FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyFootnotePresentation, buildFootnoteContentMap, calculateFootnoteReservedHeights, collectFootnoteRefs, convertFootnoteToContent, mapFootnotesToPages };
|
|
440
|
+
export { FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyFootnotePresentation, buildFootnoteContentMap, calculateFootnoteReservedHeights, collectEndnoteRefs, collectFootnoteRefs, computeNoteDisplayNumbers, convertFootnoteToContent, mapFootnotesToPages, remapNoteMarkerText };
|
|
@@ -20,13 +20,11 @@ declare function calculateHeaderFooterVisualBounds(blocks: FlowBlock[], measures
|
|
|
20
20
|
visualBottom: number;
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
|
-
* Compute
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* page-hash invalidation signal), but the margin extender needs the flow-only
|
|
29
|
-
* extent.
|
|
23
|
+
* Compute flow-only header/footer bounds, excluding anchored/floating objects
|
|
24
|
+
* such as full-page letterheads and watermarks. Word positions those objects on
|
|
25
|
+
* the page independently from header/footer flow. Keeping them in
|
|
26
|
+
* `visualBottom` is still correct for rendering and page-hash invalidation;
|
|
27
|
+
* these bounds separately describe the in-flow portion for API consumers.
|
|
30
28
|
*/
|
|
31
29
|
declare function calculateHeaderFooterMarginPushBounds(blocks: FlowBlock[], measures: Measure[], flowHeight: number, metrics: HeaderFooterMetrics): {
|
|
32
30
|
top: number;
|
|
@@ -225,13 +225,11 @@ function calculateHeaderFooterVisualBounds(blocks, measures, flowHeight, metrics
|
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
/**
|
|
228
|
-
* Compute
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* page-hash invalidation signal), but the margin extender needs the flow-only
|
|
234
|
-
* extent.
|
|
228
|
+
* Compute flow-only header/footer bounds, excluding anchored/floating objects
|
|
229
|
+
* such as full-page letterheads and watermarks. Word positions those objects on
|
|
230
|
+
* the page independently from header/footer flow. Keeping them in
|
|
231
|
+
* `visualBottom` is still correct for rendering and page-hash invalidation;
|
|
232
|
+
* these bounds separately describe the in-flow portion for API consumers.
|
|
235
233
|
*/
|
|
236
234
|
function calculateHeaderFooterMarginPushBounds(blocks, measures, flowHeight, metrics) {
|
|
237
235
|
let top = 0;
|
|
@@ -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 };
|