@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,7 +5,12 @@ type LayoutRunReason = "font-ready" | "initial" | "layout-input" | "manual" | "t
|
|
|
5
5
|
type LayoutPhase = "flow-blocks" | "header-footer" | "initial-fonts" | "layout-document" | "measure-blocks" | "render-pages";
|
|
6
6
|
type HiddenEditorStateReason = "external-document" | "mount";
|
|
7
7
|
type HiddenEditorPhase = "editor-state" | "editor-view" | "to-prose-doc" | "update-state";
|
|
8
|
+
type DocumentLoadPhase = "docx-parse";
|
|
8
9
|
type LayoutInstrumentation = {
|
|
10
|
+
onDocumentLoadPhase?: (event: {
|
|
11
|
+
durationMs: number;
|
|
12
|
+
phase: DocumentLoadPhase;
|
|
13
|
+
}) => void;
|
|
9
14
|
onHiddenEditorPhase?: (event: {
|
|
10
15
|
durationMs: number;
|
|
11
16
|
phase: HiddenEditorPhase;
|
|
@@ -21,6 +26,9 @@ type LayoutInstrumentation = {
|
|
|
21
26
|
message: string;
|
|
22
27
|
reason: LayoutRunReason;
|
|
23
28
|
}) => void;
|
|
29
|
+
onLayoutStart?: (event: {
|
|
30
|
+
reason: LayoutRunReason;
|
|
31
|
+
}) => void;
|
|
24
32
|
onLayoutPhase?: (event: {
|
|
25
33
|
durationMs: number;
|
|
26
34
|
phase: LayoutPhase;
|
|
@@ -42,9 +50,11 @@ declare global {
|
|
|
42
50
|
declare function recordMeasureBlock(blockIndex: number, block: FlowBlock): void;
|
|
43
51
|
declare function recordMeasureBlockError(blockIndex: number, block: FlowBlock, error: unknown): void;
|
|
44
52
|
declare function recordLayoutComplete(reason?: LayoutRunReason): void;
|
|
53
|
+
declare function recordLayoutStart(reason: LayoutRunReason): void;
|
|
45
54
|
declare function recordLayoutError(reason: LayoutRunReason, error: unknown): void;
|
|
46
55
|
declare function recordLayoutPhase(reason: LayoutRunReason, phase: LayoutPhase, durationMs: number): void;
|
|
47
56
|
declare function recordHiddenEditorStateCreate(reason: HiddenEditorStateReason): void;
|
|
48
57
|
declare function recordHiddenEditorPhase(reason: HiddenEditorStateReason, phase: HiddenEditorPhase, durationMs: number): void;
|
|
58
|
+
declare function recordDocumentLoadPhase(phase: DocumentLoadPhase, durationMs: number): void;
|
|
49
59
|
//#endregion
|
|
50
|
-
export { HiddenEditorPhase, HiddenEditorStateReason, LayoutInstrumentation, LayoutPhase, LayoutRunReason, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordMeasureBlock, recordMeasureBlockError };
|
|
60
|
+
export { DocumentLoadPhase, HiddenEditorPhase, HiddenEditorStateReason, LayoutInstrumentation, LayoutPhase, LayoutRunReason, recordDocumentLoadPhase, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart, recordMeasureBlock, recordMeasureBlockError };
|
|
@@ -15,6 +15,9 @@ function recordMeasureBlockError(blockIndex, block, error) {
|
|
|
15
15
|
function recordLayoutComplete(reason = "manual") {
|
|
16
16
|
globalThis.__folioLayoutInstrumentation?.onLayoutComplete?.({ reason });
|
|
17
17
|
}
|
|
18
|
+
function recordLayoutStart(reason) {
|
|
19
|
+
globalThis.__folioLayoutInstrumentation?.onLayoutStart?.({ reason });
|
|
20
|
+
}
|
|
18
21
|
function recordLayoutError(reason, error) {
|
|
19
22
|
const message = error instanceof Error ? error.message : String(error);
|
|
20
23
|
globalThis.__folioLayoutInstrumentation?.onLayoutError?.({
|
|
@@ -39,5 +42,11 @@ function recordHiddenEditorPhase(reason, phase, durationMs) {
|
|
|
39
42
|
reason
|
|
40
43
|
});
|
|
41
44
|
}
|
|
45
|
+
function recordDocumentLoadPhase(phase, durationMs) {
|
|
46
|
+
globalThis.__folioLayoutInstrumentation?.onDocumentLoadPhase?.({
|
|
47
|
+
durationMs,
|
|
48
|
+
phase
|
|
49
|
+
});
|
|
50
|
+
}
|
|
42
51
|
//#endregion
|
|
43
|
-
export { recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordMeasureBlock, recordMeasureBlockError };
|
|
52
|
+
export { recordDocumentLoadPhase, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart, recordMeasureBlock, recordMeasureBlockError };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand } from "../types.js";
|
|
1
|
+
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, tableColumnsArePinned } from "../types.js";
|
|
2
2
|
import { findClearLineY, measureParagraph } from "./measureParagraph.js";
|
|
3
3
|
import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
|
|
4
|
+
import { getTextBoxGroupId } from "../textBoxGroup.js";
|
|
4
5
|
import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrumentation.js";
|
|
5
6
|
import { bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
|
|
6
7
|
//#region src/layout-engine/measure/measureBlocks.ts
|
|
@@ -71,12 +72,17 @@ function measureTableCellBlockVisualHeight(block, blockMeasure) {
|
|
|
71
72
|
}
|
|
72
73
|
let maxImageHeight = 0;
|
|
73
74
|
for (const run of inlineImageRuns) maxImageHeight = Math.max(maxImageHeight, run.height);
|
|
75
|
+
if (inlineImageRuns.length === 1) {
|
|
76
|
+
const spacingBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
|
|
77
|
+
const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
|
|
78
|
+
return spacingBefore + maxImageHeight + spacingAfter;
|
|
79
|
+
}
|
|
74
80
|
const spacingBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
|
|
75
81
|
const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
|
|
76
82
|
return spacingBefore + maxImageHeight + spacingAfter;
|
|
77
83
|
}
|
|
78
|
-
function getTableCellVerticalBorderHeight(cell) {
|
|
79
|
-
return (cell?.borders?.top?.width ?? 0) + (cell?.borders?.bottom?.width ?? 0);
|
|
84
|
+
function getTableCellVerticalBorderHeight(cell, isFirstRow) {
|
|
85
|
+
return (isFirstRow ? cell?.borders?.top?.width ?? 0 : 0) + (cell?.borders?.bottom?.width ?? 0);
|
|
80
86
|
}
|
|
81
87
|
function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
82
88
|
const DEFAULT_CELL_PADDING_X = 7;
|
|
@@ -113,6 +119,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
113
119
|
while (occupied.has(colIdx)) colIdx++;
|
|
114
120
|
}
|
|
115
121
|
}
|
|
122
|
+
const columnsPinned = tableColumnsArePinned(tableBlock);
|
|
116
123
|
const rows = tableBlock.rows.map((row, rowIdx) => {
|
|
117
124
|
let columnIndex = 0;
|
|
118
125
|
const occupied = occupiedColumnsPerRow.get(rowIdx) ?? /* @__PURE__ */ new Set();
|
|
@@ -128,7 +135,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
128
135
|
const padLeft = cell.padding?.left ?? DEFAULT_CELL_PADDING_X;
|
|
129
136
|
const padRight = cell.padding?.right ?? DEFAULT_CELL_PADDING_X;
|
|
130
137
|
const cellContentWidth = Math.max(1, cellWidth - padLeft - padRight);
|
|
131
|
-
const measureWidth = cell.noWrap ? NO_WRAP_MEASURE_WIDTH : cellContentWidth;
|
|
138
|
+
const measureWidth = cell.noWrap === true && !columnsPinned ? NO_WRAP_MEASURE_WIDTH : cellContentWidth;
|
|
132
139
|
const cellMeasure = {
|
|
133
140
|
blocks: cell.blocks.map((b) => measureBlock(b, measureWidth, void 0, void 0, fieldValues)),
|
|
134
141
|
width: cellWidth,
|
|
@@ -143,8 +150,19 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
143
150
|
});
|
|
144
151
|
for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
|
|
145
152
|
const row = rows[rowIdx];
|
|
153
|
+
const sourceRow = tableBlock.rows[rowIdx];
|
|
154
|
+
if (sourceRow?.hidden) {
|
|
155
|
+
row.height = 0;
|
|
156
|
+
row.cells = sourceRow.cells.map(() => ({
|
|
157
|
+
blocks: [],
|
|
158
|
+
width: 0,
|
|
159
|
+
height: 0
|
|
160
|
+
}));
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
146
163
|
const sourceRowCells = tableBlock.rows[rowIdx]?.cells;
|
|
147
164
|
let maxCellHeightWithBorders = 0;
|
|
165
|
+
let maxBorderHeight = 0;
|
|
148
166
|
for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
|
|
149
167
|
const cell = row.cells[cellIdx];
|
|
150
168
|
const sourceCell = sourceRowCells?.[cellIdx];
|
|
@@ -158,13 +176,14 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
158
176
|
const padTop = sourceCell?.padding?.top ?? DEFAULT_CELL_PADDING_Y;
|
|
159
177
|
const padBottom = sourceCell?.padding?.bottom ?? DEFAULT_CELL_PADDING_Y;
|
|
160
178
|
cell.height += padTop + padBottom;
|
|
161
|
-
|
|
179
|
+
const borderHeight = getTableCellVerticalBorderHeight(sourceCell, rowIdx === 0);
|
|
180
|
+
maxCellHeightWithBorders = Math.max(maxCellHeightWithBorders, cell.height + borderHeight);
|
|
181
|
+
maxBorderHeight = Math.max(maxBorderHeight, borderHeight);
|
|
162
182
|
}
|
|
163
|
-
const sourceRow = tableBlock.rows[rowIdx];
|
|
164
183
|
const explicitHeight = sourceRow?.height;
|
|
165
184
|
const heightRule = sourceRow?.heightRule;
|
|
166
185
|
if (explicitHeight && heightRule === "exact") row.height = explicitHeight;
|
|
167
|
-
else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight);
|
|
186
|
+
else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight + maxBorderHeight);
|
|
168
187
|
else row.height = maxCellHeightWithBorders;
|
|
169
188
|
}
|
|
170
189
|
const totalHeight = rows.reduce((h, r) => h + r.height, 0);
|
|
@@ -183,6 +202,7 @@ function perBlockNumberValue(value, blockIndex, fallback) {
|
|
|
183
202
|
}
|
|
184
203
|
function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry) {
|
|
185
204
|
const zones = [];
|
|
205
|
+
const textBoxGroupAnchors = /* @__PURE__ */ new Map();
|
|
186
206
|
const defaultMarginTop = Array.isArray(marginTop) ? marginTop[0] ?? 0 : marginTop;
|
|
187
207
|
const pageHeightInput = pageGeometry?.pageHeight ?? 0;
|
|
188
208
|
const marginBottomInput = pageGeometry?.marginBottom ?? 0;
|
|
@@ -272,28 +292,31 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
272
292
|
const block = blocks[blockIndex];
|
|
273
293
|
if (block.kind !== "textBox") continue;
|
|
274
294
|
const tb = block;
|
|
275
|
-
if (!floatingTextBoxReservesBand(tb)) continue;
|
|
295
|
+
if (!floatingTextBoxReservesBand(tb) || tb.position === void 0) continue;
|
|
276
296
|
const v = tb.position?.vertical;
|
|
277
|
-
if (!isPageFrameRelativeAnchor(v?.relativeTo)) continue;
|
|
278
297
|
const height = measureTextBoxBlock(tb).height;
|
|
279
298
|
const distTop = tb.distTop ?? 0;
|
|
280
299
|
const distBottom = tb.distBottom ?? 0;
|
|
281
300
|
const blockMarginTop = perBlockNumberValue(marginTop, blockIndex, defaultMarginTop);
|
|
282
|
-
const
|
|
301
|
+
const pageFrameRelative = isPageFrameRelativeAnchor(v?.relativeTo);
|
|
302
|
+
const rawTop = pageFrameRelative ? bandTopContentY(v, {
|
|
283
303
|
pageHeight: perBlockNumberValue(pageHeightInput, blockIndex, defaultPageHeight),
|
|
284
304
|
marginTop: blockMarginTop,
|
|
285
305
|
marginBottom: perBlockNumberValue(marginBottomInput, blockIndex, defaultMarginBottom),
|
|
286
306
|
boxHeight: height
|
|
287
|
-
});
|
|
307
|
+
}) : emuToPixels(v?.posOffset ?? 0);
|
|
288
308
|
const bottomY = rawTop + height + distBottom;
|
|
289
309
|
if (bottomY <= 0) continue;
|
|
310
|
+
const groupId = getTextBoxGroupId(tb);
|
|
311
|
+
const anchorBlockIndex = groupId ? textBoxGroupAnchors.get(groupId) ?? blockIndex : blockIndex;
|
|
312
|
+
if (groupId && !textBoxGroupAnchors.has(groupId)) textBoxGroupAnchors.set(groupId, blockIndex);
|
|
290
313
|
zones.push({
|
|
291
314
|
leftMargin: 0,
|
|
292
315
|
rightMargin: 0,
|
|
293
316
|
topY: Math.max(0, rawTop - distTop),
|
|
294
317
|
bottomY,
|
|
295
|
-
anchorBlockIndex
|
|
296
|
-
isMarginRelative: true,
|
|
318
|
+
anchorBlockIndex,
|
|
319
|
+
...pageFrameRelative ? { isMarginRelative: true } : {},
|
|
297
320
|
fullWidthBlock: true
|
|
298
321
|
});
|
|
299
322
|
}
|
|
@@ -353,14 +376,15 @@ function measureTextBoxBlock(tb, fieldValues) {
|
|
|
353
376
|
};
|
|
354
377
|
}
|
|
355
378
|
/**
|
|
356
|
-
* `true` for a section break that starts a new page
|
|
357
|
-
* `oddPage
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* band that lands right after the break.
|
|
379
|
+
* `true` for a section break that explicitly starts a new page
|
|
380
|
+
* (`nextPage`/`evenPage`/`oddPage`). An absent type follows
|
|
381
|
+
* `scheduleSectionBreak` and stays continuous, so measurement must retain any
|
|
382
|
+
* active exclusion zones across it. Used by `measureBlocks` to reset the
|
|
383
|
+
* running Y for a page-pinned band that lands right after the break.
|
|
384
|
+
* eigenpal #694.
|
|
361
385
|
*/
|
|
362
386
|
function isNextPageSectionBreak(block) {
|
|
363
|
-
return block.kind === "sectionBreak" && block.type !== "continuous";
|
|
387
|
+
return block.kind === "sectionBreak" && block.type !== void 0 && block.type !== "continuous";
|
|
364
388
|
}
|
|
365
389
|
/**
|
|
366
390
|
* Measure all blocks with floating image support.
|
|
@@ -21,6 +21,12 @@ const DEFAULT_FONT_SIZE = 11;
|
|
|
21
21
|
const DEFAULT_FONT_FAMILY = "Calibri";
|
|
22
22
|
const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
23
23
|
const WIDTH_TOLERANCE = .5;
|
|
24
|
+
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
25
|
+
const JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO = .017;
|
|
26
|
+
const JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO = .025;
|
|
27
|
+
const JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO = .021;
|
|
28
|
+
const DEFAULT_LIST_HANGING_INDENT_PX = 24;
|
|
29
|
+
const ALL_CAPS_RATIO_THRESHOLD = .8;
|
|
24
30
|
/**
|
|
25
31
|
* Find the longest prefix of `text` that fits within `maxWidth` pixels.
|
|
26
32
|
* Returns the number of characters that fit (at least 1 if `forceMin` is true).
|
|
@@ -266,6 +272,18 @@ function hasFollowingTabOnLine(runs, tabIndex) {
|
|
|
266
272
|
}
|
|
267
273
|
return false;
|
|
268
274
|
}
|
|
275
|
+
function hasPriorTabOnLine(runs, tabIndex) {
|
|
276
|
+
for (let i = tabIndex - 1; i >= 0; i--) {
|
|
277
|
+
const prior = runs[i];
|
|
278
|
+
if (!prior || isLineBreakRun(prior) || isImageRun(prior) && isBlockLayoutImageRun(prior)) break;
|
|
279
|
+
if (isTabRun(prior)) return true;
|
|
280
|
+
}
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
function canClampTabToRightEdge(alignment, currentLineWidth, hasPriorTab, followingWidth, availableWidth) {
|
|
284
|
+
if (alignment === "start" || alignment === "default") return currentLineWidth > WIDTH_TOLERANCE && (hasPriorTab || followingWidth <= availableWidth);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
269
287
|
/**
|
|
270
288
|
* Width of the inline content preceding the first `.` in the runs that follow
|
|
271
289
|
* a tab, used to anchor `decimal` tab stops. Mirrors `getTextAfterTab` +
|
|
@@ -299,6 +317,33 @@ function measureDecimalPrefixWidthAfterTab(runs, tabIndex, fieldValues) {
|
|
|
299
317
|
function isSpaceOrTab(char) {
|
|
300
318
|
return char === " " || char === " ";
|
|
301
319
|
}
|
|
320
|
+
function uppercaseLetterRatio(text) {
|
|
321
|
+
let letters = 0;
|
|
322
|
+
let uppercase = 0;
|
|
323
|
+
for (const char of text) {
|
|
324
|
+
const lower = char.toLocaleLowerCase();
|
|
325
|
+
const upper = char.toLocaleUpperCase();
|
|
326
|
+
if (lower === upper) continue;
|
|
327
|
+
letters++;
|
|
328
|
+
if (char === upper) uppercase++;
|
|
329
|
+
}
|
|
330
|
+
return letters === 0 ? 0 : uppercase / letters;
|
|
331
|
+
}
|
|
332
|
+
function justifyShrinkToleranceRatio(block, isFirstLine, regularSpaceCount, nonBreakingSpaceCount) {
|
|
333
|
+
if (block.attrs?.listMarker !== void 0) {
|
|
334
|
+
if ((block.attrs.indent?.hanging ?? 0) <= DEFAULT_LIST_HANGING_INDENT_PX) return JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
335
|
+
if (isFirstLine) return JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO;
|
|
336
|
+
const totalSpaces = regularSpaceCount + nonBreakingSpaceCount;
|
|
337
|
+
if (totalSpaces === 0) return JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO;
|
|
338
|
+
return Math.max(JUSTIFY_SHRINK_TOLERANCE_RATIO, JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO * (regularSpaceCount / totalSpaces));
|
|
339
|
+
}
|
|
340
|
+
const hasTabStops = (block.attrs?.tabs?.length ?? 0) > 0;
|
|
341
|
+
const hasTabRuns = block.runs.some(isTabRun);
|
|
342
|
+
if (!isFirstLine && hasTabRuns && !hasTabStops) return JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO;
|
|
343
|
+
if ((isFirstLine || hasTabStops) && (hasTabStops || hasTabRuns)) return (block.attrs?.indent?.firstLine ?? 0) === 0 ? JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO : JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
344
|
+
if (uppercaseLetterRatio(block.runs.map((run) => isTextRun(run) ? run.text ?? "" : "").join("")) > ALL_CAPS_RATIO_THRESHOLD) return JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
345
|
+
return JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO;
|
|
346
|
+
}
|
|
302
347
|
function trimTrailingSpacesAndTabs(text) {
|
|
303
348
|
let end = text.length;
|
|
304
349
|
while (end > 0) {
|
|
@@ -384,6 +429,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
384
429
|
const runs = block.runs;
|
|
385
430
|
const attrs = block.attrs;
|
|
386
431
|
const spacing = attrs?.spacing;
|
|
432
|
+
const isJustifiedParagraph = attrs?.alignment === "justify";
|
|
387
433
|
const floatingZones = options?.floatingZones;
|
|
388
434
|
const paragraphYOffset = options?.paragraphYOffset ?? 0;
|
|
389
435
|
const indent = attrs?.indent;
|
|
@@ -477,6 +523,9 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
477
523
|
toRun: 0,
|
|
478
524
|
toChar: 0,
|
|
479
525
|
width: 0,
|
|
526
|
+
trailingWhitespaceWidth: 0,
|
|
527
|
+
regularSpaceCount: 0,
|
|
528
|
+
nonBreakingSpaceCount: 0,
|
|
480
529
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
481
530
|
maxFontMetrics: null,
|
|
482
531
|
maxImageHeightPx: 0,
|
|
@@ -523,7 +572,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
523
572
|
fromChar: currentLine.fromChar,
|
|
524
573
|
toRun: currentLine.toRun,
|
|
525
574
|
toChar: currentLine.toChar,
|
|
526
|
-
width: currentLine.width,
|
|
575
|
+
width: Math.max(0, currentLine.width - currentLine.trailingWhitespaceWidth),
|
|
527
576
|
...finalTypography
|
|
528
577
|
};
|
|
529
578
|
if (currentLine.leftOffset > 0) line.leftOffset = currentLine.leftOffset;
|
|
@@ -543,17 +592,21 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
543
592
|
const estimatedLineHeight = ptToPx(DEFAULT_FONT_SIZE) * DEFAULT_LINE_HEIGHT_MULTIPLIER;
|
|
544
593
|
skipObstructingFloats(estimatedLineHeight, bodyContentWidth);
|
|
545
594
|
const floatingMargins = getFloatingMargins(cumulativeHeight, estimatedLineHeight, floatingZones, paragraphYOffset);
|
|
595
|
+
const adjustedWidth = Math.max(1, getFloatingAvailableWidth(floatingMargins, bodyContentWidth));
|
|
546
596
|
currentLine = {
|
|
547
597
|
fromRun: runIndex,
|
|
548
598
|
fromChar: charIndex,
|
|
549
599
|
toRun: runIndex,
|
|
550
600
|
toChar: charIndex,
|
|
551
601
|
width: 0,
|
|
602
|
+
trailingWhitespaceWidth: 0,
|
|
603
|
+
regularSpaceCount: 0,
|
|
604
|
+
nonBreakingSpaceCount: 0,
|
|
552
605
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
553
606
|
maxFontMetrics: null,
|
|
554
607
|
maxImageHeightPx: 0,
|
|
555
608
|
maxMathHeightPx: 0,
|
|
556
|
-
availableWidth:
|
|
609
|
+
availableWidth: adjustedWidth,
|
|
557
610
|
leftOffset: floatingMargins.leftMargin,
|
|
558
611
|
rightOffset: floatingMargins.rightMargin,
|
|
559
612
|
...floatingMargins.segments?.length ? { segmentZones: floatingMargins.segments } : {}
|
|
@@ -589,20 +642,23 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
589
642
|
const lineX = currentLine.width + currentLine.leftOffset;
|
|
590
643
|
const isFirstLine = lines.length === 0;
|
|
591
644
|
const contentX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + lineX;
|
|
592
|
-
|
|
645
|
+
const tabResult = calculateTabWidth(contentX, {
|
|
593
646
|
...attrs?.tabs !== void 0 ? { explicitStops: attrs.tabs } : {},
|
|
647
|
+
...attrs?.defaultTabStopTwips !== void 0 ? { defaultTabInterval: attrs.defaultTabStopTwips } : {},
|
|
594
648
|
leftIndent: pixelsToTwips(indentLeft)
|
|
595
649
|
}, {
|
|
596
650
|
followingWidth,
|
|
597
651
|
decimalPrefixWidth
|
|
598
|
-
})
|
|
652
|
+
});
|
|
653
|
+
let tabWidth = tabResult.width;
|
|
599
654
|
const lineRightEdgeX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + currentLine.availableWidth + currentLine.leftOffset;
|
|
600
|
-
if (!hasFollowingTabOnLine(runs, runIndex) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
|
|
655
|
+
if (!hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width, hasPriorTabOnLine(runs, runIndex), followingWidth, currentLine.availableWidth) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
|
|
601
656
|
if (currentLine.width + tabWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
|
|
602
657
|
startNewLine(runIndex, 0);
|
|
603
658
|
updateMaxFont(style);
|
|
604
659
|
}
|
|
605
660
|
currentLine.width += tabWidth;
|
|
661
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
606
662
|
currentLine.toRun = runIndex;
|
|
607
663
|
currentLine.toChar = 1;
|
|
608
664
|
continue;
|
|
@@ -632,6 +688,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
632
688
|
const imageFootprintPx = imageHeight + (run.distTop ?? 0) + (run.distBottom ?? 0);
|
|
633
689
|
if (imageFootprintPx > currentLine.maxImageHeightPx) currentLine.maxImageHeightPx = imageFootprintPx;
|
|
634
690
|
currentLine.width += imageWidth;
|
|
691
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
635
692
|
currentLine.toRun = runIndex;
|
|
636
693
|
currentLine.toChar = 1;
|
|
637
694
|
continue;
|
|
@@ -646,6 +703,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
646
703
|
updateMaxFont(style);
|
|
647
704
|
}
|
|
648
705
|
currentLine.width += fieldWidth;
|
|
706
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
649
707
|
currentLine.toRun = runIndex;
|
|
650
708
|
currentLine.toChar = 1;
|
|
651
709
|
continue;
|
|
@@ -666,6 +724,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
666
724
|
}
|
|
667
725
|
if (mathFootprintPx > currentLine.maxMathHeightPx) currentLine.maxMathHeightPx = mathFootprintPx;
|
|
668
726
|
currentLine.width += mathWidth;
|
|
727
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
669
728
|
currentLine.toRun = runIndex;
|
|
670
729
|
currentLine.toChar = 1;
|
|
671
730
|
continue;
|
|
@@ -690,12 +749,17 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
690
749
|
break;
|
|
691
750
|
}
|
|
692
751
|
const word = text.slice(charIndex, nextBreak);
|
|
693
|
-
const
|
|
694
|
-
|
|
752
|
+
const measuredWord = trimTrailingSpacesAndTabs(word);
|
|
753
|
+
const wordWidth = measureTextWidth(measuredWord, style);
|
|
754
|
+
const fullWordWidth = measureTextWidth(word, style);
|
|
755
|
+
const regularSpaces = measuredWord.split(" ").length - 1;
|
|
756
|
+
const nonBreakingSpaces = measuredWord.split("\xA0").length - 1;
|
|
757
|
+
const widthTolerance = isJustifiedParagraph ? Math.max(WIDTH_TOLERANCE, currentLine.availableWidth * justifyShrinkToleranceRatio(block, lines.length === 0, currentLine.regularSpaceCount + regularSpaces, currentLine.nonBreakingSpaceCount + nonBreakingSpaces)) : WIDTH_TOLERANCE;
|
|
758
|
+
if (wordWidth > currentLine.availableWidth + widthTolerance) {
|
|
695
759
|
let chunkStart = 0;
|
|
696
|
-
while (chunkStart <
|
|
760
|
+
while (chunkStart < measuredWord.length) {
|
|
697
761
|
const spaceLeft = currentLine.availableWidth - currentLine.width + WIDTH_TOLERANCE;
|
|
698
|
-
let bestEnd = findMaxFittingLength(
|
|
762
|
+
let bestEnd = findMaxFittingLength(measuredWord.slice(chunkStart), style, spaceLeft);
|
|
699
763
|
if (bestEnd === 0) {
|
|
700
764
|
if (currentLine.width > 0) {
|
|
701
765
|
startNewLine(runIndex, charIndex + chunkStart);
|
|
@@ -705,26 +769,39 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
705
769
|
bestEnd = 1;
|
|
706
770
|
}
|
|
707
771
|
const chunkEnd = chunkStart + bestEnd;
|
|
708
|
-
const
|
|
772
|
+
const chunk = measuredWord.slice(chunkStart, chunkEnd);
|
|
773
|
+
const chunkWidth = measureTextWidth(chunk, style);
|
|
709
774
|
currentLine.width += chunkWidth;
|
|
775
|
+
currentLine.trailingWhitespaceWidth = chunkWidth - measureTextWidth(trimTrailingSpacesAndTabs(chunk), style);
|
|
776
|
+
currentLine.regularSpaceCount += chunk.split(" ").length - 1;
|
|
777
|
+
currentLine.nonBreakingSpaceCount += chunk.split("\xA0").length - 1;
|
|
710
778
|
currentLine.toRun = runIndex;
|
|
711
779
|
currentLine.toChar = charIndex + chunkEnd;
|
|
712
780
|
chunkStart = chunkEnd;
|
|
713
|
-
if (chunkStart <
|
|
781
|
+
if (chunkStart < measuredWord.length) {
|
|
714
782
|
startNewLine(runIndex, charIndex + chunkStart);
|
|
715
783
|
updateMaxFont(lineHeightStyle);
|
|
716
784
|
}
|
|
717
785
|
}
|
|
786
|
+
const trailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
787
|
+
currentLine.width += trailingWhitespaceWidth;
|
|
788
|
+
currentLine.trailingWhitespaceWidth = trailingWhitespaceWidth;
|
|
789
|
+
currentLine.regularSpaceCount += word.split(" ").length - 1;
|
|
790
|
+
currentLine.nonBreakingSpaceCount += word.split("\xA0").length - 1;
|
|
791
|
+
currentLine.toChar = nextBreak;
|
|
718
792
|
charIndex = nextBreak;
|
|
719
793
|
continue;
|
|
720
794
|
}
|
|
721
795
|
const rawGlueWidth = nextBreak === text.length && word.length > 0 && !isBreakChar(word[word.length - 1]) ? trailingGlueWidths[runIndex] ?? 0 : 0;
|
|
722
|
-
const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() +
|
|
723
|
-
if (currentLine.width > 0 && currentLine.width + wordWidth + glueWidth > currentLine.availableWidth +
|
|
796
|
+
const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() + widthTolerance ? rawGlueWidth : 0;
|
|
797
|
+
if (currentLine.width > 0 && currentLine.width + wordWidth + glueWidth > currentLine.availableWidth + widthTolerance) {
|
|
724
798
|
startNewLine(runIndex, charIndex);
|
|
725
799
|
updateMaxFont(lineHeightStyle);
|
|
726
800
|
}
|
|
727
|
-
currentLine.width +=
|
|
801
|
+
currentLine.width += fullWordWidth;
|
|
802
|
+
currentLine.trailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
803
|
+
currentLine.regularSpaceCount += word.split(" ").length - 1;
|
|
804
|
+
currentLine.nonBreakingSpaceCount += word.split("\xA0").length - 1;
|
|
728
805
|
currentLine.toRun = runIndex;
|
|
729
806
|
currentLine.toChar = nextBreak;
|
|
730
807
|
charIndex = nextBreak;
|
|
@@ -27,7 +27,8 @@ type PaginatorOptions = {
|
|
|
27
27
|
w: number;
|
|
28
28
|
h: number;
|
|
29
29
|
}; /** Page margins. */
|
|
30
|
-
margins: PageMargins;
|
|
30
|
+
margins: PageMargins; /** Swap left/right margins on even physical pages. */
|
|
31
|
+
mirrorMargins?: boolean;
|
|
31
32
|
/**
|
|
32
33
|
* Margins applied only to the very first page (page 1) of the paginator.
|
|
33
34
|
* Used when a `<w:titlePg/>`-enabled section needs different margins for
|
|
@@ -46,7 +46,7 @@ function createPaginator(options) {
|
|
|
46
46
|
}
|
|
47
47
|
function currentPageUsesActiveLayout(state) {
|
|
48
48
|
if (pendingPageSize || pendingMargins) return false;
|
|
49
|
-
return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins,
|
|
49
|
+
return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins, getPageMargins(state.page.number));
|
|
50
50
|
}
|
|
51
51
|
if (getContentHeight() <= 0) panic("Paginator: page size and margins yield no content area");
|
|
52
52
|
let columnWidth = calculateColumnWidth(pageSize.w, margins.left, margins.right, columns);
|
|
@@ -62,14 +62,20 @@ function createPaginator(options) {
|
|
|
62
62
|
recalculateColumnWidth();
|
|
63
63
|
}
|
|
64
64
|
function getPageMargins(pageNumber) {
|
|
65
|
-
|
|
65
|
+
const pageMargins = pageNumber === 1 && options.firstPageMargins ? { ...options.firstPageMargins } : { ...margins };
|
|
66
|
+
if (options.mirrorMargins === true && pageNumber % 2 === 0) return {
|
|
67
|
+
...pageMargins,
|
|
68
|
+
left: pageMargins.right,
|
|
69
|
+
right: pageMargins.left
|
|
70
|
+
};
|
|
71
|
+
return pageMargins;
|
|
66
72
|
}
|
|
67
73
|
let columnRegionTop = margins.top;
|
|
68
74
|
/**
|
|
69
75
|
* Get X position for a given column index.
|
|
70
76
|
*/
|
|
71
77
|
function getColumnX(columnIndex) {
|
|
72
|
-
return margins.left + columnIndex * (columnWidth + columns.gap);
|
|
78
|
+
return (states.at(-1)?.page.margins.left ?? getPageMargins(1).left) + columnIndex * (columnWidth + columns.gap);
|
|
73
79
|
}
|
|
74
80
|
/**
|
|
75
81
|
* Create a new page and add it to the list.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TextBoxBlock } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/textBoxGroup.d.ts
|
|
4
|
+
declare const setTextBoxGroupId: (textBox: TextBoxBlock, groupId: string) => void;
|
|
5
|
+
declare const getTextBoxGroupId: (textBox: TextBoxBlock) => string | undefined;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { getTextBoxGroupId, setTextBoxGroupId };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/layout-engine/textBoxGroup.ts
|
|
2
|
+
const textBoxGroupIds = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
const setTextBoxGroupId = (textBox, groupId) => {
|
|
4
|
+
textBoxGroupIds.set(textBox, groupId);
|
|
5
|
+
};
|
|
6
|
+
const getTextBoxGroupId = (textBox) => textBoxGroupIds.get(textBox);
|
|
7
|
+
//#endregion
|
|
8
|
+
export { getTextBoxGroupId, setTextBoxGroupId };
|
|
@@ -3,7 +3,7 @@ import { ImagePosition, ImageWrap, SdtProperties, SdtType, TableWidthType } from
|
|
|
3
3
|
|
|
4
4
|
//#region src/layout-engine/types.d.ts
|
|
5
5
|
declare namespace types_d_exports {
|
|
6
|
-
export { 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 };
|
|
6
|
+
export { 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 };
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Unique identifier for a block in the document.
|
|
@@ -303,7 +303,9 @@ type ParagraphAttrs = {
|
|
|
303
303
|
indent?: ParagraphIndent;
|
|
304
304
|
keepNext?: boolean;
|
|
305
305
|
keepLines?: boolean;
|
|
306
|
-
|
|
306
|
+
widowControl?: boolean;
|
|
307
|
+
pageBreakBefore?: boolean; /** Word's cached pagination hint (`w:lastRenderedPageBreak`). */
|
|
308
|
+
renderedPageBreakBefore?: boolean;
|
|
307
309
|
styleId?: string;
|
|
308
310
|
contextualSpacing?: boolean;
|
|
309
311
|
/**
|
|
@@ -432,6 +434,7 @@ type TableRow = {
|
|
|
432
434
|
height?: number;
|
|
433
435
|
heightRule?: "auto" | "atLeast" | "exact";
|
|
434
436
|
isHeader?: boolean;
|
|
437
|
+
hidden?: boolean;
|
|
435
438
|
};
|
|
436
439
|
/**
|
|
437
440
|
* Floating table positioning info (pixel values).
|
|
@@ -457,7 +460,14 @@ type TableBlock = {
|
|
|
457
460
|
rows: TableRow[];
|
|
458
461
|
columnWidths?: number[]; /** Table width value (twips for dxa, 50ths of percent for pct). */
|
|
459
462
|
width?: number; /** Table width type ('auto', 'pct', 'dxa', 'nil'). */
|
|
460
|
-
widthType?: TableWidthType;
|
|
463
|
+
widthType?: TableWidthType;
|
|
464
|
+
/**
|
|
465
|
+
* Table layout algorithm (`w:tblLayout`). "fixed" pins columns to their grid
|
|
466
|
+
* widths; "autofit" (the default when absent) lets Word size columns to
|
|
467
|
+
* content. Consulted alongside `widthType` to decide whether `w:noWrap` cells
|
|
468
|
+
* can be honored (see `tableColumnsArePinned`).
|
|
469
|
+
*/
|
|
470
|
+
layout?: "fixed" | "autofit"; /** Table horizontal alignment */
|
|
461
471
|
justification?: "left" | "center" | "right"; /** Table indent from left margin (in pixels, from w:tblInd) */
|
|
462
472
|
indent?: number; /** Right-to-left column order (w:bidiVisual): logical column 0 paints on the right. */
|
|
463
473
|
bidi?: boolean; /** Floating table properties (pixel values). */
|
|
@@ -518,6 +528,7 @@ type SectionBreakBlock = {
|
|
|
518
528
|
};
|
|
519
529
|
type PageHeaderFooterRefs = {
|
|
520
530
|
titlePg?: boolean;
|
|
531
|
+
evenAndOddHeaders?: boolean;
|
|
521
532
|
headerDefault?: string;
|
|
522
533
|
headerFirst?: string;
|
|
523
534
|
headerEven?: string;
|
|
@@ -900,7 +911,8 @@ type LayoutOptions = {
|
|
|
900
911
|
headerContentHeights?: HeaderFooterContentHeights; /** Footer content heights by variant. */
|
|
901
912
|
footerContentHeights?: HeaderFooterContentHeights; /** Whether section has different first page header/footer. */
|
|
902
913
|
titlePage?: boolean; /** Whether section has different even/odd headers/footers. */
|
|
903
|
-
evenAndOddHeaders?: boolean; /**
|
|
914
|
+
evenAndOddHeaders?: boolean; /** Swap left/right margins on even physical pages. */
|
|
915
|
+
mirrorMargins?: boolean; /** Per-page footnote reserved heights (pageNumber → height in pixels). */
|
|
904
916
|
footnoteReservedHeights?: Map<number, number>;
|
|
905
917
|
/**
|
|
906
918
|
* Footnote content heights keyed by internal footnote id (the OOXML
|
|
@@ -967,10 +979,8 @@ type HeaderFooterContent = {
|
|
|
967
979
|
visualTop?: number; /** Bottom-most visual extent relative to the nominal flow origin. */
|
|
968
980
|
visualBottom?: number;
|
|
969
981
|
/**
|
|
970
|
-
* Flow-only bounds
|
|
971
|
-
*
|
|
972
|
-
* the renderer paints behind body content; including them would reserve
|
|
973
|
-
* a full-page letterhead as body push-down.
|
|
982
|
+
* Flow-only bounds for consumers that need to distinguish normal
|
|
983
|
+
* header/footer flow from floating or `behindDoc` artwork.
|
|
974
984
|
*/
|
|
975
985
|
marginPushTop?: number;
|
|
976
986
|
marginPushBottom?: number;
|
|
@@ -1028,5 +1038,19 @@ declare function floatingTextBoxWrapsText(block: TextBoxFlowAttrs): boolean;
|
|
|
1028
1038
|
* Ported from eigenpal #694.
|
|
1029
1039
|
*/
|
|
1030
1040
|
declare function floatingTextBoxReservesBand(block: TextBoxFlowAttrs): boolean;
|
|
1041
|
+
/**
|
|
1042
|
+
* A table's columns are pinned when the layout engine cannot widen a column to
|
|
1043
|
+
* fit content that overflows it: either the layout is explicitly fixed
|
|
1044
|
+
* (`w:tblLayout w:type="fixed"`) or an explicit total width (`w:tblW` `dxa`/
|
|
1045
|
+
* `pct`) is fully consumed by the grid.
|
|
1046
|
+
*
|
|
1047
|
+
* In OOXML, `w:noWrap` (§17.4.30) asks Word not to soft-wrap a cell, but Word
|
|
1048
|
+
* can only honor it by widening the column. When the columns are pinned it
|
|
1049
|
+
* cannot, so overflowing content wraps despite `w:noWrap`. An auto-width table
|
|
1050
|
+
* (`w:tblW` `auto`/`nil`/absent with autofit layout) lets Word widen the column
|
|
1051
|
+
* instead, so there `w:noWrap` genuinely keeps content on one line. Measurement
|
|
1052
|
+
* and the painter both consult this so their line count and `white-space` agree.
|
|
1053
|
+
*/
|
|
1054
|
+
declare function tableColumnsArePinned(table: TableBlock): boolean;
|
|
1031
1055
|
//#endregion
|
|
1032
|
-
export { 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, types_d_exports };
|
|
1056
|
+
export { 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, types_d_exports };
|
|
@@ -11,7 +11,8 @@ var types_exports = /* @__PURE__ */ __exportAll({
|
|
|
11
11
|
floatingTextBoxWrapsText: () => floatingTextBoxWrapsText,
|
|
12
12
|
isFloatingImageRun: () => isFloatingImageRun,
|
|
13
13
|
isFloatingTextBoxBlock: () => isFloatingTextBoxBlock,
|
|
14
|
-
isTextWrappingFloatingImageRun: () => isTextWrappingFloatingImageRun
|
|
14
|
+
isTextWrappingFloatingImageRun: () => isTextWrappingFloatingImageRun,
|
|
15
|
+
tableColumnsArePinned: () => tableColumnsArePinned
|
|
15
16
|
});
|
|
16
17
|
/** Default internal margins for text boxes (OOXML defaults in pixels) */
|
|
17
18
|
const DEFAULT_TEXTBOX_MARGINS = {
|
|
@@ -96,5 +97,22 @@ function floatingTextBoxWrapsText(block) {
|
|
|
96
97
|
function floatingTextBoxReservesBand(block) {
|
|
97
98
|
return isFloatingTextBoxBlock(block) && block.wrapType === "topAndBottom";
|
|
98
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* A table's columns are pinned when the layout engine cannot widen a column to
|
|
102
|
+
* fit content that overflows it: either the layout is explicitly fixed
|
|
103
|
+
* (`w:tblLayout w:type="fixed"`) or an explicit total width (`w:tblW` `dxa`/
|
|
104
|
+
* `pct`) is fully consumed by the grid.
|
|
105
|
+
*
|
|
106
|
+
* In OOXML, `w:noWrap` (§17.4.30) asks Word not to soft-wrap a cell, but Word
|
|
107
|
+
* can only honor it by widening the column. When the columns are pinned it
|
|
108
|
+
* cannot, so overflowing content wraps despite `w:noWrap`. An auto-width table
|
|
109
|
+
* (`w:tblW` `auto`/`nil`/absent with autofit layout) lets Word widen the column
|
|
110
|
+
* instead, so there `w:noWrap` genuinely keeps content on one line. Measurement
|
|
111
|
+
* and the painter both consult this so their line count and `white-space` agree.
|
|
112
|
+
*/
|
|
113
|
+
function tableColumnsArePinned(table) {
|
|
114
|
+
if (table.layout === "fixed") return true;
|
|
115
|
+
return table.widthType === "dxa" || table.widthType === "pct";
|
|
116
|
+
}
|
|
99
117
|
//#endregion
|
|
100
|
-
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, types_exports };
|
|
118
|
+
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_exports };
|