@stll/folio-core 0.6.1 → 0.7.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/controller/layoutPipeline.js +18 -10
- package/dist/docx/groupDrawingParser.d.ts +1 -1
- package/dist/docx/groupDrawingParser.js +49 -8
- package/dist/docx/runParser.js +11 -2
- package/dist/docx/server/boundedArchive.d.ts +24 -0
- package/dist/docx/server/boundedArchive.js +106 -0
- package/dist/docx/server/extractDocxText.d.ts +23 -0
- package/dist/docx/server/extractDocxText.js +154 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +64 -10
- package/dist/layout-engine/index.js +115 -16
- package/dist/layout-engine/keep-together.d.ts +7 -5
- package/dist/layout-engine/keep-together.js +20 -4
- package/dist/layout-engine/measure/cache.js +2 -0
- package/dist/layout-engine/measure/measureParagraph.js +30 -12
- package/dist/layout-engine/paginator.js +10 -2
- package/dist/layout-engine/types.d.ts +15 -3
- package/dist/layout-painter/renderParagraph.js +88 -5
- package/dist/prosemirror/conversion/toProseDoc.js +2 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -0
- package/dist/prosemirror/schema/nodes.d.ts +2 -1
- package/dist/prosemirror/utils/tabCalculator.js +1 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +3 -1
- package/dist/utils/formatToStyle.js +3 -3
- package/dist/utils/units.d.ts +6 -6
- package/dist/utils/units.js +8 -8
- package/package.json +1 -1
|
@@ -44,9 +44,7 @@ function collectSectionConfigs(blocks, initialConfig, finalConfig) {
|
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Whether a paragraph block has no visible content (no runs, or a single
|
|
47
|
-
* empty text run).
|
|
48
|
-
* paragraphs (only direct `<w:pPr><w:spacing>` formatting survives) — see
|
|
49
|
-
* eigenpal #402.
|
|
47
|
+
* empty text run).
|
|
50
48
|
*/
|
|
51
49
|
function isEmptyParagraph(block) {
|
|
52
50
|
if (block.runs.length === 0) return true;
|
|
@@ -68,16 +66,27 @@ function pageHasVisibleBodyContent(page, blocksById) {
|
|
|
68
66
|
}
|
|
69
67
|
return false;
|
|
70
68
|
}
|
|
69
|
+
function continuesNumberedSequence(previous, current) {
|
|
70
|
+
if (previous?.kind !== "paragraph" || current.kind !== "paragraph") return false;
|
|
71
|
+
const previousNumPr = previous.attrs?.numPr;
|
|
72
|
+
const currentNumPr = current.attrs?.numPr;
|
|
73
|
+
if (previousNumPr?.numId === void 0 || currentNumPr?.numId === void 0) return false;
|
|
74
|
+
return previousNumPr.numId === currentNumPr.numId && previousNumPr.ilvl === currentNumPr.ilvl;
|
|
75
|
+
}
|
|
76
|
+
function pageStartsWithPreviousParagraphContinuation(page, previousBlock) {
|
|
77
|
+
if (previousBlock?.kind !== "paragraph") return false;
|
|
78
|
+
return page.fragments.some((fragment) => fragment.kind === "paragraph" && String(fragment.blockId) === String(previousBlock.id) && fragment.continuesFromPrev === true);
|
|
79
|
+
}
|
|
71
80
|
/**
|
|
72
81
|
* Get spacing before a paragraph block. Empty paragraphs whose
|
|
73
82
|
* `before` came only from the implicit default paragraph style collapse to
|
|
74
|
-
* zero.
|
|
75
|
-
*
|
|
83
|
+
* zero. Reference layout keeps inherited spacing when the empty paragraph itself is
|
|
84
|
+
* authored through direct `w:pPr` formatting or an explicit `w:pStyle`.
|
|
76
85
|
*/
|
|
77
86
|
function getSpacingBefore(block) {
|
|
78
87
|
const value = block.attrs?.spacing?.before ?? 0;
|
|
79
88
|
if (value === 0) return 0;
|
|
80
|
-
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.spacingExplicit?.before) return 0;
|
|
89
|
+
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.hasDirectParagraphFormatting && !block.attrs?.spacingExplicit?.before) return 0;
|
|
81
90
|
return value;
|
|
82
91
|
}
|
|
83
92
|
/**
|
|
@@ -87,9 +96,59 @@ function getSpacingBefore(block) {
|
|
|
87
96
|
function getSpacingAfter(block) {
|
|
88
97
|
const value = block.attrs?.spacing?.after ?? 0;
|
|
89
98
|
if (value === 0) return 0;
|
|
90
|
-
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.spacingExplicit?.after) return 0;
|
|
99
|
+
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.hasDirectParagraphFormatting && !block.attrs?.spacingExplicit?.after) return 0;
|
|
91
100
|
return value;
|
|
92
101
|
}
|
|
102
|
+
function balancedParagraphSectionHeight({ blocks, measures, startIndex, endIndex, incomingSpacing, columnCount, availableHeight }) {
|
|
103
|
+
if (columnCount <= 1 || startIndex >= endIndex || availableHeight <= 0) return;
|
|
104
|
+
const lineUnits = [];
|
|
105
|
+
let totalHeight = 0;
|
|
106
|
+
let tallestUnit = 0;
|
|
107
|
+
let trailingSpacing = incomingSpacing;
|
|
108
|
+
for (let index = startIndex; index < endIndex; index++) {
|
|
109
|
+
const block = blocks[index];
|
|
110
|
+
const measure = measures[index];
|
|
111
|
+
if (block?.kind !== "paragraph" || measure?.kind !== "paragraph") return;
|
|
112
|
+
if (block.attrs?.keepNext === true || block.attrs?.keepLines === true || block.runs.some((run) => run.kind === "text" && run.footnoteRefId !== void 0)) return;
|
|
113
|
+
const leadingSpacing = Math.max(getSpacingBefore(block), trailingSpacing);
|
|
114
|
+
if (measure.lines.length === 0) {
|
|
115
|
+
if (leadingSpacing > 0) {
|
|
116
|
+
lineUnits.push(leadingSpacing);
|
|
117
|
+
totalHeight += leadingSpacing;
|
|
118
|
+
tallestUnit = Math.max(tallestUnit, leadingSpacing);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (let lineIndex = 0; lineIndex < measure.lines.length; lineIndex++) {
|
|
122
|
+
const line = measure.lines[lineIndex];
|
|
123
|
+
if (!line) continue;
|
|
124
|
+
const unitHeight = measuredLineAdvance(line) + (lineIndex === 0 ? leadingSpacing : 0);
|
|
125
|
+
lineUnits.push(unitHeight);
|
|
126
|
+
totalHeight += unitHeight;
|
|
127
|
+
tallestUnit = Math.max(tallestUnit, unitHeight);
|
|
128
|
+
}
|
|
129
|
+
if (tallestUnit > availableHeight || totalHeight > availableHeight * columnCount) return;
|
|
130
|
+
trailingSpacing = getSpacingAfter(block);
|
|
131
|
+
}
|
|
132
|
+
if (totalHeight <= 0) return;
|
|
133
|
+
const columnsNeeded = (targetHeight) => {
|
|
134
|
+
let usedHeight = 0;
|
|
135
|
+
let usedColumns = 1;
|
|
136
|
+
for (const unitHeight of lineUnits) if (usedHeight > 0 && usedHeight + unitHeight > targetHeight) {
|
|
137
|
+
usedColumns += 1;
|
|
138
|
+
usedHeight = unitHeight;
|
|
139
|
+
} else usedHeight += unitHeight;
|
|
140
|
+
return usedColumns;
|
|
141
|
+
};
|
|
142
|
+
let lower = Math.max(tallestUnit, totalHeight / columnCount);
|
|
143
|
+
let upper = Math.min(totalHeight, availableHeight);
|
|
144
|
+
if (columnsNeeded(upper) > columnCount) return;
|
|
145
|
+
for (let iteration = 0; iteration < 32; iteration++) {
|
|
146
|
+
const middle = (lower + upper) / 2;
|
|
147
|
+
if (columnsNeeded(middle) <= columnCount) upper = middle;
|
|
148
|
+
else lower = middle;
|
|
149
|
+
}
|
|
150
|
+
return Math.ceil(upper * 1e3) / 1e3;
|
|
151
|
+
}
|
|
93
152
|
function hasWidowControl(block) {
|
|
94
153
|
return block.attrs?.widowControl !== false;
|
|
95
154
|
}
|
|
@@ -123,6 +182,24 @@ function applyContextualSpacing(blocks) {
|
|
|
123
182
|
for (const block of blocks) if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) applyContextualSpacing(cell.blocks);
|
|
124
183
|
else if (block.kind === "textBox") applyContextualSpacing(block.content);
|
|
125
184
|
}
|
|
185
|
+
/** Suppress automatic inter-item spacing within one numbered sequence. */
|
|
186
|
+
const applyAutomaticListSpacing = (blocks) => {
|
|
187
|
+
for (let index = 1; index < blocks.length; index++) {
|
|
188
|
+
const previous = blocks[index - 1];
|
|
189
|
+
const current = blocks[index];
|
|
190
|
+
if (previous?.kind !== "paragraph" || current?.kind !== "paragraph" || !continuesNumberedSequence(previous, current)) continue;
|
|
191
|
+
if (previous.attrs?.automaticSpacing?.after && previous.attrs.spacing) previous.attrs.spacing = {
|
|
192
|
+
...previous.attrs.spacing,
|
|
193
|
+
after: 0
|
|
194
|
+
};
|
|
195
|
+
if (current.attrs?.automaticSpacing?.before && current.attrs.spacing) current.attrs.spacing = {
|
|
196
|
+
...current.attrs.spacing,
|
|
197
|
+
before: 0
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
for (const block of blocks) if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) applyAutomaticListSpacing(cell.blocks);
|
|
201
|
+
else if (block.kind === "textBox") applyAutomaticListSpacing(block.content);
|
|
202
|
+
};
|
|
126
203
|
/**
|
|
127
204
|
* Layout a document: convert blocks + measures into pages with positioned fragments.
|
|
128
205
|
*
|
|
@@ -171,6 +248,7 @@ function layoutDocument(blocks, measures, options) {
|
|
|
171
248
|
...options.sectionHeaderFooterRefs !== void 0 ? { sectionHeaderFooterRefs: options.sectionHeaderFooterRefs } : {}
|
|
172
249
|
});
|
|
173
250
|
applyContextualSpacing(blocks);
|
|
251
|
+
applyAutomaticListSpacing(blocks);
|
|
174
252
|
const keepNextChains = computeKeepNextChains(blocks);
|
|
175
253
|
const midChainIndices = getMidChainIndices(keepNextChains);
|
|
176
254
|
const blocksById = /* @__PURE__ */ new Map();
|
|
@@ -179,7 +257,7 @@ function layoutDocument(blocks, measures, options) {
|
|
|
179
257
|
let activeSectionMarginTop = initialConfig.margins.top;
|
|
180
258
|
let activeSectionPageHeight = initialConfig.pageSize.h;
|
|
181
259
|
let activeSectionMarginBottom = initialConfig.margins.bottom;
|
|
182
|
-
let naturalPageAdvanceSinceRenderedBreak =
|
|
260
|
+
let naturalPageAdvanceSinceRenderedBreak = "none";
|
|
183
261
|
for (let i = 0; i < blocks.length; i++) {
|
|
184
262
|
const block = blocks[i];
|
|
185
263
|
const measure = measures[i];
|
|
@@ -187,13 +265,15 @@ function layoutDocument(blocks, measures, options) {
|
|
|
187
265
|
if (hasPageBreakBefore(block)) paginator.forcePageBreak();
|
|
188
266
|
else if (hasRenderedPageBreak) {
|
|
189
267
|
const state = paginator.getCurrentState();
|
|
190
|
-
if (!(block.kind === "paragraph" && isPaginationEmptyParagraph(block) && naturalPageAdvanceSinceRenderedBreak) && pageHasVisibleBodyContent(state.page, blocksById)) paginator.forcePageBreak();
|
|
268
|
+
if (!(pageStartsWithPreviousParagraphContinuation(state.page, blocks[i - 1]) || naturalPageAdvanceSinceRenderedBreak === "reflowBoundary" || block.kind === "paragraph" && isPaginationEmptyParagraph(block) && naturalPageAdvanceSinceRenderedBreak !== "none" || naturalPageAdvanceSinceRenderedBreak !== "none" && continuesNumberedSequence(blocks[i - 1], block)) && pageHasVisibleBodyContent(state.page, blocksById)) paginator.forcePageBreak();
|
|
191
269
|
}
|
|
192
|
-
if (hasRenderedPageBreak || hasPageBreakBefore(block)) naturalPageAdvanceSinceRenderedBreak =
|
|
270
|
+
if (hasRenderedPageBreak || hasPageBreakBefore(block)) naturalPageAdvanceSinceRenderedBreak = "none";
|
|
193
271
|
const chain = keepNextChains.get(i);
|
|
194
272
|
if (chain && !midChainIndices.has(i)) {
|
|
195
273
|
const chainHeight = calculateChainHeight(chain, blocks, measures);
|
|
274
|
+
const pageBeforeChainLayout = paginator.getCurrentState().page.number;
|
|
196
275
|
paginator.ensureFits(chainHeight);
|
|
276
|
+
if (paginator.getCurrentState().page.number > pageBeforeChainLayout) naturalPageAdvanceSinceRenderedBreak = "reflowBoundary";
|
|
197
277
|
}
|
|
198
278
|
const pageBeforeBlockLayout = paginator.getCurrentState().page.number;
|
|
199
279
|
switch (block.kind) {
|
|
@@ -224,6 +304,23 @@ function layoutDocument(blocks, measures, options) {
|
|
|
224
304
|
case "sectionBreak": {
|
|
225
305
|
const nextSectionConfig = sectionConfigs[sectionIdx + 1] ?? initialConfig;
|
|
226
306
|
handleSectionBreak(block, paginator, nextSectionConfig, sectionBreakTypes[sectionIdx + 1] ?? sectionBreakTypes[sectionIdx], sectionIdx + 1);
|
|
307
|
+
const nextColumns = nextSectionConfig.columns;
|
|
308
|
+
const nextBreakIndex = breakIndices[sectionIdx + 1] ?? blocks.length;
|
|
309
|
+
const nextBreak = blocks[nextBreakIndex];
|
|
310
|
+
const sectionEndsContinuously = nextBreakIndex === blocks.length || nextBreak?.kind === "sectionBreak" && nextBreak.type === "continuous";
|
|
311
|
+
if (nextColumns && sectionEndsContinuously) {
|
|
312
|
+
const state = paginator.getCurrentState();
|
|
313
|
+
const balancedHeight = balancedParagraphSectionHeight({
|
|
314
|
+
blocks,
|
|
315
|
+
measures,
|
|
316
|
+
startIndex: i + 1,
|
|
317
|
+
endIndex: nextBreakIndex,
|
|
318
|
+
incomingSpacing: state.trailingSpacing,
|
|
319
|
+
columnCount: nextColumns.count,
|
|
320
|
+
availableHeight: paginator.getAvailableHeight()
|
|
321
|
+
});
|
|
322
|
+
if (balancedHeight !== void 0) state.contentBottom = Math.min(state.contentBottom, state.cursorY + balancedHeight);
|
|
323
|
+
}
|
|
227
324
|
activeSectionMarginTop = nextSectionConfig.margins.top;
|
|
228
325
|
activeSectionPageHeight = nextSectionConfig.pageSize.h;
|
|
229
326
|
activeSectionMarginBottom = nextSectionConfig.margins.bottom;
|
|
@@ -233,8 +330,8 @@ function layoutDocument(blocks, measures, options) {
|
|
|
233
330
|
default: break;
|
|
234
331
|
}
|
|
235
332
|
const isVisibleBodyBlock = block.kind === "paragraph" ? !isEmptyParagraph(block) : block.kind !== "pageBreak" && block.kind !== "columnBreak" && block.kind !== "sectionBreak";
|
|
236
|
-
if (block.kind === "pageBreak" || block.kind === "columnBreak" || block.kind === "sectionBreak") naturalPageAdvanceSinceRenderedBreak =
|
|
237
|
-
else if (isVisibleBodyBlock && paginator.getCurrentState().page.number > pageBeforeBlockLayout) naturalPageAdvanceSinceRenderedBreak =
|
|
333
|
+
if (block.kind === "pageBreak" || block.kind === "columnBreak" || block.kind === "sectionBreak") naturalPageAdvanceSinceRenderedBreak = "none";
|
|
334
|
+
else if (isVisibleBodyBlock && paginator.getCurrentState().page.number > pageBeforeBlockLayout) naturalPageAdvanceSinceRenderedBreak = paginator.pages[pageBeforeBlockLayout - 1]?.fragments.some(({ blockId }) => blockId === block.id) ? "reflowBoundary" : "ordinary";
|
|
238
335
|
}
|
|
239
336
|
if (paginator.pages.length === 0) paginator.getCurrentState();
|
|
240
337
|
return {
|
|
@@ -429,9 +526,10 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
429
526
|
let x = paginator.getColumnX(columnIndex);
|
|
430
527
|
if (block.justification === "center") x += (paginator.columnWidth - measure.totalWidth) / 2;
|
|
431
528
|
else if (block.justification === "right") x = x + paginator.columnWidth - measure.totalWidth;
|
|
529
|
+
else if (block.indent !== void 0) x += block.indent;
|
|
432
530
|
else {
|
|
433
531
|
const leadingCellMargin = block.rows.at(0)?.cells.at(0)?.padding?.left ?? 0;
|
|
434
|
-
x
|
|
532
|
+
x -= leadingCellMargin;
|
|
435
533
|
}
|
|
436
534
|
return x;
|
|
437
535
|
};
|
|
@@ -638,9 +736,10 @@ function layoutFloatingTable(block, measure, paginator, contentWidth) {
|
|
|
638
736
|
else if (spec === "center") y = baseY + (contentHeight - tableHeight) / 2;
|
|
639
737
|
}
|
|
640
738
|
if (!usedExplicitY) y = paginator.ensureFits(tableHeight).cursorY;
|
|
641
|
-
const
|
|
642
|
-
const
|
|
643
|
-
const
|
|
739
|
+
const usesNumericOffset = floating?.tblpX !== void 0;
|
|
740
|
+
const clampToPage = floating?.horzAnchor === "page" || usesNumericOffset;
|
|
741
|
+
const minX = clampToPage ? 0 : margins.left;
|
|
742
|
+
const maxX = clampToPage ? page.size.w - tableWidth : margins.left + contentWidth - tableWidth;
|
|
644
743
|
if (Number.isFinite(maxX)) x = Math.max(minX, Math.min(x, maxX));
|
|
645
744
|
const fragment = {
|
|
646
745
|
kind: "table",
|
|
@@ -2,8 +2,9 @@ import { FlowBlock, Measure } from "./types.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/layout-engine/keep-together.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* A chain of
|
|
6
|
-
*
|
|
5
|
+
* A chain of paragraphs that Word keeps with following content. This includes
|
|
6
|
+
* explicit keepNext links and a trailing table separator that would otherwise
|
|
7
|
+
* be stranded at the bottom of a page.
|
|
7
8
|
*/
|
|
8
9
|
type KeepNextChain = {
|
|
9
10
|
/** Index of the first paragraph in the chain. */startIndex: number; /** Index of the last keepNext or pass-through empty member. */
|
|
@@ -14,9 +15,10 @@ type KeepNextChain = {
|
|
|
14
15
|
/**
|
|
15
16
|
* Pre-scan blocks to find all keepNext chains.
|
|
16
17
|
*
|
|
17
|
-
* A
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* A chain starts with a paragraph whose keepNext=true or with an empty
|
|
19
|
+
* separator immediately following a table. It continues through further
|
|
20
|
+
* keepNext paragraphs and structural empty separators. The first visible
|
|
21
|
+
* non-keepNext paragraph is its anchor.
|
|
20
22
|
*
|
|
21
23
|
* Returns a map from chain start index to chain info.
|
|
22
24
|
*/
|
|
@@ -5,12 +5,23 @@ function isVisuallyEmptyParagraph(block) {
|
|
|
5
5
|
const run = block.runs.at(0);
|
|
6
6
|
return run?.kind === "text" && run.text === "";
|
|
7
7
|
}
|
|
8
|
+
function startsTrailingTableSeparatorChain(blocks, index) {
|
|
9
|
+
const block = blocks[index];
|
|
10
|
+
if (block?.kind !== "paragraph" || !isVisuallyEmptyParagraph(block) || blocks[index - 1]?.kind !== "table") return false;
|
|
11
|
+
for (let nextIndex = index + 1; nextIndex < blocks.length; nextIndex++) {
|
|
12
|
+
const nextBlock = blocks[nextIndex];
|
|
13
|
+
if (nextBlock?.kind !== "paragraph") return false;
|
|
14
|
+
if (!isVisuallyEmptyParagraph(nextBlock)) return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
8
18
|
/**
|
|
9
19
|
* Pre-scan blocks to find all keepNext chains.
|
|
10
20
|
*
|
|
11
|
-
* A
|
|
12
|
-
*
|
|
13
|
-
*
|
|
21
|
+
* A chain starts with a paragraph whose keepNext=true or with an empty
|
|
22
|
+
* separator immediately following a table. It continues through further
|
|
23
|
+
* keepNext paragraphs and structural empty separators. The first visible
|
|
24
|
+
* non-keepNext paragraph is its anchor.
|
|
14
25
|
*
|
|
15
26
|
* Returns a map from chain start index to chain info.
|
|
16
27
|
*/
|
|
@@ -21,7 +32,7 @@ function computeKeepNextChains(blocks) {
|
|
|
21
32
|
if (processed.has(i)) continue;
|
|
22
33
|
const block = blocks[i];
|
|
23
34
|
if (block.kind !== "paragraph") continue;
|
|
24
|
-
if (!block.attrs?.keepNext) continue;
|
|
35
|
+
if (!block.attrs?.keepNext && !startsTrailingTableSeparatorChain(blocks, i)) continue;
|
|
25
36
|
const memberIndices = [i];
|
|
26
37
|
let endIndex = i;
|
|
27
38
|
for (let j = i + 1; j < blocks.length; j++) {
|
|
@@ -67,6 +78,7 @@ function calculateChainHeight(chain, blocks, measures) {
|
|
|
67
78
|
if (firstBlock?.kind !== "paragraph" || firstMeasure?.kind !== "paragraph") return 0;
|
|
68
79
|
let totalHeight = (firstBlock.attrs?.spacing?.before ?? 0) + firstMeasure.totalHeight;
|
|
69
80
|
let trailingSpacing = firstBlock.attrs?.spacing?.after ?? 0;
|
|
81
|
+
const startsWithTrailingTableSeparator = blocks[firstMemberIndex - 1]?.kind === "table" && isVisuallyEmptyParagraph(firstBlock) && !firstBlock.attrs?.keepNext;
|
|
70
82
|
const successorIndices = [...chain.memberIndices.slice(1)];
|
|
71
83
|
if (chain.anchorIndex !== -1) successorIndices.push(chain.anchorIndex);
|
|
72
84
|
for (let index = 0; index < successorIndices.length; index++) {
|
|
@@ -80,6 +92,10 @@ function calculateChainHeight(chain, blocks, measures) {
|
|
|
80
92
|
if (!firstLine) return totalHeight;
|
|
81
93
|
const isAnchor = index === successorIndices.length - 1 && chain.anchorIndex !== -1;
|
|
82
94
|
const isSplittable = successorMeasure.lines.length > 1 && !successorBlock.attrs?.keepLines;
|
|
95
|
+
if (isAnchor && startsWithTrailingTableSeparator && successorBlock.attrs?.widowControl !== false && successorMeasure.lines.length > 1) {
|
|
96
|
+
const secondLine = successorMeasure.lines.at(1);
|
|
97
|
+
return totalHeight + firstLine.lineHeight + (secondLine?.lineHeight ?? 0);
|
|
98
|
+
}
|
|
83
99
|
if (isAnchor || isSplittable) return totalHeight + firstLine.lineHeight;
|
|
84
100
|
totalHeight += successorMeasure.totalHeight;
|
|
85
101
|
trailingSpacing = successorBlock.attrs?.spacing?.after ?? 0;
|
|
@@ -160,11 +160,13 @@ function hashParagraphBlock(block) {
|
|
|
160
160
|
const attrs = block.attrs;
|
|
161
161
|
if (attrs) {
|
|
162
162
|
if (attrs.alignment) parts.push(`align:${attrs.alignment}`);
|
|
163
|
+
if (attrs.outlineLevel !== void 0) parts.push(`outline:${attrs.outlineLevel}`);
|
|
163
164
|
if (attrs.indent) parts.push(`indent:${attrs.indent.left}|${attrs.indent.right}|${attrs.indent.firstLine}|${attrs.indent.hanging}`);
|
|
164
165
|
if (attrs.spacing) parts.push(`spacing:${attrs.spacing.before}|${attrs.spacing.after}|${attrs.spacing.line}|${attrs.spacing.lineRule}`);
|
|
165
166
|
if (attrs.defaultFontSize != null) parts.push(`dfs:${attrs.defaultFontSize}`);
|
|
166
167
|
if (attrs.defaultFontFamily != null) parts.push(`dff:${attrs.defaultFontFamily}`);
|
|
167
168
|
if (attrs.suppressEmptyParagraphHeight) parts.push("sup");
|
|
169
|
+
if (attrs.reserveEmptyOutlineHeight) parts.push("outline-empty-reserve");
|
|
168
170
|
const borders = attrs.borders;
|
|
169
171
|
if (borders) {
|
|
170
172
|
const signature = (border) => border ? `${border.width ?? ""},${border.style ?? ""},${border.color ?? ""}` : "";
|
|
@@ -329,20 +329,34 @@ function uppercaseLetterRatio(text) {
|
|
|
329
329
|
}
|
|
330
330
|
return letters === 0 ? 0 : uppercase / letters;
|
|
331
331
|
}
|
|
332
|
+
function fixedSpaceAdjustedShrinkTolerance({ tolerance, regularSpaceCount, nonBreakingSpaceCount }) {
|
|
333
|
+
const totalSpaces = regularSpaceCount + nonBreakingSpaceCount;
|
|
334
|
+
if (totalSpaces === 0) return tolerance;
|
|
335
|
+
return Math.max(JUSTIFY_SHRINK_TOLERANCE_RATIO, tolerance * (regularSpaceCount / totalSpaces));
|
|
336
|
+
}
|
|
332
337
|
function justifyShrinkToleranceRatio(block, isFirstLine, regularSpaceCount, nonBreakingSpaceCount) {
|
|
333
338
|
if (block.attrs?.listMarker !== void 0) {
|
|
334
|
-
|
|
335
|
-
if (isFirstLine) return JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO;
|
|
336
|
-
const
|
|
337
|
-
if (
|
|
338
|
-
return
|
|
339
|
+
const hanging = block.attrs.indent?.hanging ?? 0;
|
|
340
|
+
if (isFirstLine) return hanging <= DEFAULT_LIST_HANGING_INDENT_PX ? JUSTIFY_SHRINK_TOLERANCE_RATIO : JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO;
|
|
341
|
+
const left = block.attrs.indent?.left ?? 0;
|
|
342
|
+
if (hanging > 0 && left > hanging) return JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
343
|
+
return fixedSpaceAdjustedShrinkTolerance({
|
|
344
|
+
tolerance: JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO,
|
|
345
|
+
regularSpaceCount,
|
|
346
|
+
nonBreakingSpaceCount
|
|
347
|
+
});
|
|
339
348
|
}
|
|
340
349
|
const hasTabStops = (block.attrs?.tabs?.length ?? 0) > 0;
|
|
341
350
|
const hasTabRuns = block.runs.some(isTabRun);
|
|
351
|
+
if (isFirstLine && hasTabRuns && (block.attrs?.indent?.hanging ?? 0) > 0) return JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
342
352
|
if (!isFirstLine && hasTabRuns && !hasTabStops) return JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO;
|
|
343
|
-
if (
|
|
353
|
+
if (hasTabRuns && (isFirstLine || hasTabStops)) return (block.attrs?.indent?.firstLine ?? 0) === 0 ? JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO : JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
344
354
|
if (uppercaseLetterRatio(block.runs.map((run) => isTextRun(run) ? run.text ?? "" : "").join("")) > ALL_CAPS_RATIO_THRESHOLD) return JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
345
|
-
return
|
|
355
|
+
return fixedSpaceAdjustedShrinkTolerance({
|
|
356
|
+
tolerance: JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO,
|
|
357
|
+
regularSpaceCount,
|
|
358
|
+
nonBreakingSpaceCount
|
|
359
|
+
});
|
|
346
360
|
}
|
|
347
361
|
function trimTrailingSpacesAndTabs(text) {
|
|
348
362
|
let end = text.length;
|
|
@@ -479,15 +493,17 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
479
493
|
};
|
|
480
494
|
}
|
|
481
495
|
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY);
|
|
496
|
+
const outlineLineHeight = attrs?.reserveEmptyOutlineHeight ? emptyMetrics.lineHeight * 2 : emptyMetrics.lineHeight;
|
|
482
497
|
lines.push({
|
|
483
498
|
fromRun: 0,
|
|
484
499
|
fromChar: 0,
|
|
485
500
|
toRun: 0,
|
|
486
501
|
toChar: 0,
|
|
487
502
|
width: 0,
|
|
488
|
-
...emptyMetrics
|
|
503
|
+
...emptyMetrics,
|
|
504
|
+
lineHeight: outlineLineHeight
|
|
489
505
|
});
|
|
490
|
-
let totalHeight =
|
|
506
|
+
let totalHeight = outlineLineHeight;
|
|
491
507
|
if (spacing?.before) totalHeight += spacing.before;
|
|
492
508
|
if (spacing?.after) totalHeight += spacing.after;
|
|
493
509
|
return {
|
|
@@ -651,8 +667,9 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
651
667
|
decimalPrefixWidth
|
|
652
668
|
});
|
|
653
669
|
let tabWidth = tabResult.width;
|
|
670
|
+
const landsOnLeftIndent = tabResult.alignment === "start" && indentLeft > 0 && Math.abs(contentX + tabWidth - indentLeft) <= WIDTH_TOLERANCE;
|
|
654
671
|
const lineRightEdgeX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + currentLine.availableWidth + currentLine.leftOffset;
|
|
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);
|
|
672
|
+
if (!landsOnLeftIndent && !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);
|
|
656
673
|
if (currentLine.width + tabWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
|
|
657
674
|
startNewLine(runIndex, 0);
|
|
658
675
|
updateMaxFont(style);
|
|
@@ -794,12 +811,13 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
794
811
|
}
|
|
795
812
|
const rawGlueWidth = nextBreak === text.length && word.length > 0 && !isBreakChar(word[word.length - 1]) ? trailingGlueWidths[runIndex] ?? 0 : 0;
|
|
796
813
|
const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() + widthTolerance ? rawGlueWidth : 0;
|
|
797
|
-
if (currentLine.width > 0 && currentLine.width + wordWidth + glueWidth > currentLine.availableWidth + widthTolerance) {
|
|
814
|
+
if (wordWidth > 0 && currentLine.width > 0 && currentLine.width + wordWidth + glueWidth > currentLine.availableWidth + widthTolerance) {
|
|
798
815
|
startNewLine(runIndex, charIndex);
|
|
799
816
|
updateMaxFont(lineHeightStyle);
|
|
800
817
|
}
|
|
801
818
|
currentLine.width += fullWordWidth;
|
|
802
|
-
|
|
819
|
+
const wordTrailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
820
|
+
currentLine.trailingWhitespaceWidth = wordWidth === 0 ? currentLine.trailingWhitespaceWidth + wordTrailingWhitespaceWidth : wordTrailingWhitespaceWidth;
|
|
803
821
|
currentLine.regularSpaceCount += word.split(" ").length - 1;
|
|
804
822
|
currentLine.nonBreakingSpaceCount += word.split("\xA0").length - 1;
|
|
805
823
|
currentLine.toRun = runIndex;
|
|
@@ -72,6 +72,7 @@ function createPaginator(options) {
|
|
|
72
72
|
return pageMargins;
|
|
73
73
|
}
|
|
74
74
|
let columnRegionTop = margins.top;
|
|
75
|
+
let columnRegionMaxBottom = margins.top;
|
|
75
76
|
/**
|
|
76
77
|
* Get X position for a given column index.
|
|
77
78
|
*/
|
|
@@ -114,6 +115,7 @@ function createPaginator(options) {
|
|
|
114
115
|
pages.push(page);
|
|
115
116
|
states.push(state);
|
|
116
117
|
columnRegionTop = topMargin;
|
|
118
|
+
columnRegionMaxBottom = topMargin;
|
|
117
119
|
if (options.onNewPage) options.onNewPage(state);
|
|
118
120
|
return state;
|
|
119
121
|
}
|
|
@@ -143,6 +145,7 @@ function createPaginator(options) {
|
|
|
143
145
|
*/
|
|
144
146
|
function advanceColumn(state) {
|
|
145
147
|
if (state.columnIndex < columns.count - 1) {
|
|
148
|
+
columnRegionMaxBottom = Math.max(columnRegionMaxBottom, state.cursorY);
|
|
146
149
|
state.columnIndex += 1;
|
|
147
150
|
state.cursorY = columnRegionTop;
|
|
148
151
|
state.trailingSpacing = 0;
|
|
@@ -159,7 +162,7 @@ function createPaginator(options) {
|
|
|
159
162
|
let state = getCurrentState();
|
|
160
163
|
const safeHeight = Number.isFinite(height) && height > 0 ? height : 0;
|
|
161
164
|
while (!fits(safeHeight, state)) {
|
|
162
|
-
if (safeHeight > state.contentBottom -
|
|
165
|
+
if (safeHeight > state.contentBottom - columnRegionTop) {
|
|
163
166
|
if (state.cursorY !== state.topMargin) state = advanceColumn(state);
|
|
164
167
|
return state;
|
|
165
168
|
}
|
|
@@ -276,12 +279,17 @@ function createPaginator(options) {
|
|
|
276
279
|
* column advancement stays below existing content (for continuous breaks).
|
|
277
280
|
*/
|
|
278
281
|
function updateColumns(newColumns) {
|
|
282
|
+
const previousColumnCount = columns.count;
|
|
283
|
+
const state = getCurrentState();
|
|
284
|
+
const previousRegionBottom = Math.max(columnRegionMaxBottom, state.cursorY);
|
|
279
285
|
columns = newColumns;
|
|
280
286
|
recalculateColumnWidths();
|
|
281
|
-
const state = getCurrentState();
|
|
282
287
|
if (columns.count > 1) state.page.columns = { ...columns };
|
|
283
288
|
else delete state.page.columns;
|
|
289
|
+
state.contentBottom = state.rawContentBottom - state.footnoteHeight;
|
|
290
|
+
if (previousColumnCount > 1) state.cursorY = previousRegionBottom;
|
|
284
291
|
columnRegionTop = state.cursorY;
|
|
292
|
+
columnRegionMaxBottom = state.cursorY;
|
|
285
293
|
state.columnIndex = 0;
|
|
286
294
|
}
|
|
287
295
|
function updatePageLayout(newPageSize, newMargins, applyImmediately = true) {
|
|
@@ -286,8 +286,13 @@ type ListNumPr = {
|
|
|
286
286
|
* Paragraph block attributes.
|
|
287
287
|
*/
|
|
288
288
|
type ParagraphAttrs = {
|
|
289
|
-
alignment?: "left" | "center" | "right" | "justify";
|
|
290
|
-
|
|
289
|
+
alignment?: "left" | "center" | "right" | "justify"; /** OOXML outline level (`w:outlineLvl`), where zero is the top level. */
|
|
290
|
+
outlineLevel?: number;
|
|
291
|
+
spacing?: ParagraphSpacing; /** Spacing sides resolved from OOXML automatic paragraph spacing. */
|
|
292
|
+
automaticSpacing?: {
|
|
293
|
+
before?: boolean;
|
|
294
|
+
after?: boolean;
|
|
295
|
+
};
|
|
291
296
|
/**
|
|
292
297
|
* Tracks which `spacing` sides came from inline (`<w:pPr><w:spacing>`)
|
|
293
298
|
* formatting versus inherited via paragraph style. Word collapses
|
|
@@ -300,6 +305,12 @@ type ParagraphAttrs = {
|
|
|
300
305
|
before?: boolean;
|
|
301
306
|
after?: boolean;
|
|
302
307
|
};
|
|
308
|
+
/**
|
|
309
|
+
* Whether an empty paragraph carries direct paragraph formatting in its
|
|
310
|
+
* source `w:pPr`. Word treats such a blank as authored and keeps its
|
|
311
|
+
* inherited spacing; a bare empty paragraph still collapses that spacing.
|
|
312
|
+
*/
|
|
313
|
+
hasDirectParagraphFormatting?: boolean;
|
|
303
314
|
indent?: ParagraphIndent;
|
|
304
315
|
keepNext?: boolean;
|
|
305
316
|
keepLines?: boolean;
|
|
@@ -319,7 +330,8 @@ type ParagraphAttrs = {
|
|
|
319
330
|
borders?: ParagraphBorders;
|
|
320
331
|
shading?: string;
|
|
321
332
|
tabs?: TabStop[]; /** Render structural empty paragraphs as zero-height anchors. */
|
|
322
|
-
suppressEmptyParagraphHeight?: boolean;
|
|
333
|
+
suppressEmptyParagraphHeight?: boolean; /** Reserve the reference extra line advance for a story-leading empty level-0 outline paragraph. */
|
|
334
|
+
reserveEmptyOutlineHeight?: boolean;
|
|
323
335
|
numPr?: ListNumPr;
|
|
324
336
|
listMarker?: string;
|
|
325
337
|
listIsBullet?: boolean;
|
|
@@ -709,6 +709,75 @@ function splitTextRunsByEastAsia(runs) {
|
|
|
709
709
|
}
|
|
710
710
|
return result;
|
|
711
711
|
}
|
|
712
|
+
const paintsLineEdgeSpaces = (run) => Boolean(run.underline || run.strike || run.highlight || run.shading || run.hyperlink || run.hidden || run.templatePreview || run.isInsertion || run.isDeletion || run.commentIds?.length || run.textEffect || run.emphasisMark);
|
|
713
|
+
const splitTextRunAt = (run, index) => {
|
|
714
|
+
const leading = {
|
|
715
|
+
...run,
|
|
716
|
+
text: run.text.slice(0, index)
|
|
717
|
+
};
|
|
718
|
+
const trailing = {
|
|
719
|
+
...run,
|
|
720
|
+
text: run.text.slice(index)
|
|
721
|
+
};
|
|
722
|
+
if (run.pmStart === void 0) return [leading, trailing];
|
|
723
|
+
const splitPosition = Math.min(run.pmStart + index, run.pmEnd ?? Number.POSITIVE_INFINITY);
|
|
724
|
+
leading.pmEnd = splitPosition;
|
|
725
|
+
trailing.pmStart = splitPosition;
|
|
726
|
+
return [leading, trailing];
|
|
727
|
+
};
|
|
728
|
+
/**
|
|
729
|
+
* Word keeps ordinary line-edge spaces addressable in the document model but
|
|
730
|
+
* gives them no painted advance. Collapsible trailing spaces always collapse;
|
|
731
|
+
* leading spaces collapse only after a soft wrap, not at the authored
|
|
732
|
+
* paragraph start or after a manual line break. Split paint runs to preserve
|
|
733
|
+
* exact PM ranges.
|
|
734
|
+
*/
|
|
735
|
+
const splitCollapsibleLineEdgeSpaces = (sourceRuns, collapseLeading) => {
|
|
736
|
+
const runs = [...sourceRuns];
|
|
737
|
+
const collapsedLeadingRuns = /* @__PURE__ */ new Set();
|
|
738
|
+
const collapsedTrailingRuns = /* @__PURE__ */ new Set();
|
|
739
|
+
for (let index = runs.length - 1; index >= 0; index--) {
|
|
740
|
+
const run = runs[index];
|
|
741
|
+
if (!run || !isTextRun(run)) break;
|
|
742
|
+
if (run.text.length === 0) continue;
|
|
743
|
+
const trailingSpaces = / +$/u.exec(run.text);
|
|
744
|
+
if (!trailingSpaces || paintsLineEdgeSpaces(run)) break;
|
|
745
|
+
if (trailingSpaces.index === 0) {
|
|
746
|
+
collapsedTrailingRuns.add(run);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
const [leading, trailing] = splitTextRunAt(run, trailingSpaces.index);
|
|
750
|
+
runs.splice(index, 1, leading, trailing);
|
|
751
|
+
collapsedTrailingRuns.add(trailing);
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
if (collapseLeading) for (let index = 0; index < runs.length; index++) {
|
|
755
|
+
const run = runs[index];
|
|
756
|
+
if (!run || !isTextRun(run)) break;
|
|
757
|
+
if (run.text.length === 0) continue;
|
|
758
|
+
const leadingSpaces = /^ +/u.exec(run.text);
|
|
759
|
+
if (!leadingSpaces || paintsLineEdgeSpaces(run)) break;
|
|
760
|
+
if (leadingSpaces[0].length === run.text.length) {
|
|
761
|
+
collapsedLeadingRuns.add(run);
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const [leading, trailing] = splitTextRunAt(run, leadingSpaces[0].length);
|
|
765
|
+
runs.splice(index, 1, leading, trailing);
|
|
766
|
+
collapsedLeadingRuns.add(leading);
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
runs,
|
|
771
|
+
collapsedLeadingRuns,
|
|
772
|
+
collapsedTrailingRuns
|
|
773
|
+
};
|
|
774
|
+
};
|
|
775
|
+
const startsAfterSoftWrap = (block, line) => {
|
|
776
|
+
if (line.fromChar > 0) return true;
|
|
777
|
+
if (line.fromRun === 0) return false;
|
|
778
|
+
const previousRun = block.runs.at(line.fromRun - 1);
|
|
779
|
+
return previousRun !== void 0 && !isLineBreakRun(previousRun);
|
|
780
|
+
};
|
|
712
781
|
/**
|
|
713
782
|
* Sub-pixel tolerance when comparing canvas-measured widths against the DOM's
|
|
714
783
|
* actual right edge. Accumulated rounding from canvas measureText vs. browser
|
|
@@ -878,7 +947,20 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
878
947
|
lineEl.style.boxSizing = "content-box";
|
|
879
948
|
lineEl.style.height = `${line.lineHeight}px`;
|
|
880
949
|
lineEl.style.lineHeight = `${line.lineHeight}px`;
|
|
881
|
-
const
|
|
950
|
+
const splitRuns = splitTextRunsByEastAsia(sliceRunsForLine(block, line));
|
|
951
|
+
const { runs: runsForLine, collapsedLeadingRuns: collapsedLeadingSpaceRuns, collapsedTrailingRuns: collapsedTrailingSpaceRuns } = splitCollapsibleLineEdgeSpaces(splitRuns, startsAfterSoftWrap(block, line));
|
|
952
|
+
const isCollapsedLineEdgeSpaceRun = (run) => collapsedLeadingSpaceRuns.has(run) || collapsedTrailingSpaceRuns.has(run);
|
|
953
|
+
const renderLineTextRun = (run) => {
|
|
954
|
+
const runEl = renderTextRun(run, doc);
|
|
955
|
+
if (collapsedLeadingSpaceRuns.has(run)) runEl.dataset["collapsedLeadingSpaces"] = "true";
|
|
956
|
+
if (collapsedTrailingSpaceRuns.has(run)) runEl.dataset["collapsedTrailingSpaces"] = "true";
|
|
957
|
+
if (isCollapsedLineEdgeSpaceRun(run)) {
|
|
958
|
+
runEl.style.fontSize = "0";
|
|
959
|
+
runEl.style.letterSpacing = "0";
|
|
960
|
+
runEl.style.wordSpacing = "0";
|
|
961
|
+
}
|
|
962
|
+
return runEl;
|
|
963
|
+
};
|
|
882
964
|
const onlyRun = runsForLine.length === 1 ? runsForLine[0] : void 0;
|
|
883
965
|
if (onlyRun && isMathRun(onlyRun) && onlyRun.display === "block" && block.attrs?.alignment !== "right") lineEl.style.textAlign = "center";
|
|
884
966
|
if (runsForLine.length === 1 && isImageRun(runsForLine[0])) {
|
|
@@ -914,7 +996,7 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
914
996
|
const firstLineHangingExpansionPx = hasVisibleListMarker ? Math.min(firstLineHangingPx, Math.max(0, options.leftIndentPx ?? 0)) : firstLineHangingPx;
|
|
915
997
|
const justifyCapacityPx = options.availableWidth + firstLineHangingExpansionPx;
|
|
916
998
|
const overfullPx = line.width - justifyCapacityPx;
|
|
917
|
-
const shrinkableSpaces = countShrinkableSpaces(runsForLine);
|
|
999
|
+
const shrinkableSpaces = countShrinkableSpaces(runsForLine.filter((run) => !isTextRun(run) || !isCollapsedLineEdgeSpaceRun(run)));
|
|
918
1000
|
if (overfullPx > RIGHT_EDGE_EPSILON_PX && shrinkableSpaces > 0) {
|
|
919
1001
|
lineEl.style.textAlign = "left";
|
|
920
1002
|
lineEl.style.textAlignLast = "auto";
|
|
@@ -989,7 +1071,7 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
989
1071
|
for (let j = i + 1; j < runsForLine.length; j++) {
|
|
990
1072
|
const next = runsForLine[j];
|
|
991
1073
|
if (isTabRun(next) || isLineBreakRun(next)) break;
|
|
992
|
-
if (isTextRun(next)) lineEl.append(
|
|
1074
|
+
if (isTextRun(next)) lineEl.append(renderLineTextRun(next));
|
|
993
1075
|
else if (isFieldRun(next) && options?.context) lineEl.append(renderFieldRun(next, doc, options.context));
|
|
994
1076
|
else if (isImageRun(next)) {
|
|
995
1077
|
if (isFloatingImageRun(next)) continue;
|
|
@@ -1003,13 +1085,14 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
1003
1085
|
break;
|
|
1004
1086
|
}
|
|
1005
1087
|
let tabWidth = tabResult.width;
|
|
1006
|
-
if (lineRightEdgeX !== void 0 && canClampTabToRightEdge(tabResult.alignment, i > 0, runsForLine.slice(0, i).some(isTabRun), options?.isLastLine === true) && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
|
|
1088
|
+
if (!(tabResult.alignment === "start" && leftIndentPx > 0 && Math.abs(currentX + tabWidth - leftIndentPx) <= RIGHT_EDGE_EPSILON_PX) && lineRightEdgeX !== void 0 && canClampTabToRightEdge(tabResult.alignment, i > 0, runsForLine.slice(0, i).some(isTabRun), options?.isLastLine === true) && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
|
|
1007
1089
|
const tabEl = renderTabRun(run, doc, tabWidth, tabResult.leader);
|
|
1008
1090
|
lineEl.append(tabEl);
|
|
1009
1091
|
currentX += tabWidth;
|
|
1010
1092
|
} else if (isTextRun(run)) {
|
|
1011
|
-
const runEl =
|
|
1093
|
+
const runEl = renderLineTextRun(run);
|
|
1012
1094
|
lineEl.append(runEl);
|
|
1095
|
+
if (isCollapsedLineEdgeSpaceRun(run)) continue;
|
|
1013
1096
|
const fontSize = run.fontSize || 11;
|
|
1014
1097
|
const fontFamily = run.fontFamily || "Calibri";
|
|
1015
1098
|
const measuredWidth = measureText(run.allCaps ? run.text.toLocaleUpperCase() : run.text, fontSize, fontFamily, {
|
|
@@ -549,6 +549,7 @@ function convertTable(table, styleResolver, context) {
|
|
|
549
549
|
const fallbackTableStyle = tableStyleId ? void 0 : defaultTableStyle;
|
|
550
550
|
const conditionalTableStyleId = tableStyle?.styleId ?? fallbackTableStyle?.styleId;
|
|
551
551
|
const resolvedTableBorders = table.formatting?.borders ?? tableStyle?.tblPr?.borders ?? fallbackTableStyle?.tblPr?.borders;
|
|
552
|
+
const resolvedTableIndent = table.formatting?.indent ?? tableStyle?.tblPr?.indent ?? fallbackTableStyle?.tblPr?.indent;
|
|
552
553
|
const tableCellMargins = table.formatting?.cellMargins ?? tableStyle?.tblPr?.cellMargins ?? fallbackTableStyle?.tblPr?.cellMargins;
|
|
553
554
|
let cellMarginsAttr;
|
|
554
555
|
if (tableCellMargins) {
|
|
@@ -569,6 +570,7 @@ function convertTable(table, styleResolver, context) {
|
|
|
569
570
|
if (cellMarginsAttr) attrs.cellMargins = cellMarginsAttr;
|
|
570
571
|
if (table.formatting?.look) attrs.look = table.formatting.look;
|
|
571
572
|
if (table.formatting?.borders) attrs.borders = table.formatting.borders;
|
|
573
|
+
if (resolvedTableIndent) attrs._resolvedIndent = resolvedTableIndent;
|
|
572
574
|
if (table.formatting) attrs._originalFormatting = table.formatting;
|
|
573
575
|
if (table.propertyChanges && table.propertyChanges.length > 0) attrs.tblPrChange = [...table.propertyChanges];
|
|
574
576
|
const conditionalStyles = {};
|