reamkit 1.30.0 → 1.31.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/README.md +60 -24
- package/dist/esm/core/converter/project.js +3 -1
- package/dist/esm/core/crypto/offcrypto.js +1 -1
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +63 -0
- package/dist/esm/core/font/index.d.ts +2 -0
- package/dist/esm/core/font/ttf-build.d.ts +113 -0
- package/dist/esm/core/font/ttf-build.js +1224 -0
- package/dist/esm/core/font/ttf-subset.d.ts +19 -0
- package/dist/esm/core/font/ttf-subset.js +14 -1
- package/dist/esm/core/fonts/provider.d.ts +17 -0
- package/dist/esm/core/fonts/provider.js +27 -2
- package/dist/esm/core/fonts/remote-fonts.js +8 -1
- package/dist/esm/core/ir/flow.d.ts +71 -1
- package/dist/esm/core/numbering/index.d.ts +1 -1
- package/dist/esm/core/numbering/state.d.ts +11 -1
- package/dist/esm/core/numbering/state.js +10 -1
- package/dist/esm/core/style-cascade/resolver.js +35 -4
- package/dist/esm/core/style-cascade/types.d.ts +19 -1
- package/dist/esm/core/style-cascade/types.js +3 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/layout/page-doc.d.ts +10 -3
- package/dist/esm/layout/styled-layout.d.ts +48 -7
- package/dist/esm/layout/styled-layout.js +881 -119
- package/dist/esm/layout/turned-section.d.ts +38 -0
- package/dist/esm/layout/turned-section.js +193 -0
- package/dist/esm/pdf/styled-page-emitter.js +78 -2
- package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
- package/dist/esm/pdf-reader/cff-outline.js +169 -21
- package/dist/esm/pdf-reader/content.d.ts +53 -0
- package/dist/esm/pdf-reader/content.js +9 -1
- package/dist/esm/pdf-reader/display.d.ts +36 -0
- package/dist/esm/pdf-reader/display.js +66 -1
- package/dist/esm/pdf-reader/document.js +5 -1
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
- package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
- package/dist/esm/pdf-reader/encodings.d.ts +8 -0
- package/dist/esm/pdf-reader/encodings.js +25 -3
- package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
- package/dist/esm/pdf-reader/face-outlines.js +362 -0
- package/dist/esm/pdf-reader/figures.d.ts +52 -0
- package/dist/esm/pdf-reader/figures.js +433 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
- package/dist/esm/pdf-reader/flow-build.js +345 -26
- package/dist/esm/pdf-reader/font.js +321 -36
- package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
- package/dist/esm/pdf-reader/glyf-outline.js +135 -1
- package/dist/esm/pdf-reader/glyph-names.js +154 -1
- package/dist/esm/pdf-reader/layout.d.ts +93 -0
- package/dist/esm/pdf-reader/layout.js +1703 -214
- package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
- package/dist/esm/pdf-reader/page-numbers.js +167 -0
- package/dist/esm/pdf-reader/tagged.js +182 -23
- package/dist/esm/pdf-reader/text.d.ts +6 -3
- package/dist/esm/pdf-reader/text.js +98 -9
- package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
- package/dist/esm/pdf-reader/type1-outline.js +63 -8
- package/dist/esm/pdf-reader/vector.js +71 -1
- package/dist/esm/word/doc/doc-reader.js +6 -2
- package/dist/esm/word/doc/doc-text.d.ts +6 -0
- package/dist/esm/word/doc/doc-text.js +19 -1
- package/dist/esm/word/document-parser.d.ts +2 -2
- package/dist/esm/word/document-parser.js +11 -1
- package/dist/esm/word/docx-reader.js +5 -3
- package/dist/esm/word/docx-writer.js +207 -21
- package/dist/esm/word/drawing-parser.d.ts +5 -3
- package/dist/esm/word/drawing-parser.js +49 -8
- package/dist/esm/word/font-embed.d.ts +30 -0
- package/dist/esm/word/font-embed.js +173 -0
- package/dist/esm/word/font-table.d.ts +10 -0
- package/dist/esm/word/font-table.js +13 -1
- package/dist/esm/word/index.js +1 -1
- package/dist/esm/word/numbering-parser.d.ts +3 -1
- package/dist/esm/word/numbering-parser.js +2 -1
- package/dist/esm/word/paragraph-properties.d.ts +7 -6
- package/dist/esm/word/paragraph-properties.js +14 -2
- package/dist/esm/word/run-properties.js +26 -0
- package/package.json +8 -3
|
@@ -16,6 +16,7 @@ import { FORCED_BREAK, breakLines } from "../core/line-breaker/knuth-plass.js";
|
|
|
16
16
|
import { greedyBreakLines } from "../core/line-breaker/greedy.js";
|
|
17
17
|
import { cjkBreakBetween, splitCjkSegment } from "../core/line-breaker/cjk.js";
|
|
18
18
|
import "../core/line-breaker/index.js";
|
|
19
|
+
import { formatCounter } from "../core/numbering/state.js";
|
|
19
20
|
import { DEFAULT_RESOLVED_PARAGRAPH, DEFAULT_RESOLVED_RUN } from "../core/style-cascade/types.js";
|
|
20
21
|
import { resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
|
|
21
22
|
import "../core/style-cascade/index.js";
|
|
@@ -28,6 +29,7 @@ import { layoutMath, mathGlyphSegments, variantStyle } from "./math-layout.js";
|
|
|
28
29
|
import { rectPath } from "../core/drawingml/preset-geometry.js";
|
|
29
30
|
import { buildShapePaths, buildShapeTransform, buildStroke, gradientToSolid, lineEndPaths } from "../core/drawingml/shape-render.js";
|
|
30
31
|
import { StructTreeBuilder } from "../pdf/struct-tree.js";
|
|
32
|
+
import { floatIntoFrame, turnOntoSheet } from "./turned-section.js";
|
|
31
33
|
//#region src/layout/styled-layout.ts
|
|
32
34
|
function parsePdfAProfile(pdfA) {
|
|
33
35
|
const part = Number(pdfA.charAt(6));
|
|
@@ -39,6 +41,260 @@ function parsePdfAProfile(pdfA) {
|
|
|
39
41
|
version: part === 1 ? "1.4" : "1.7"
|
|
40
42
|
};
|
|
41
43
|
}
|
|
44
|
+
function leadingOf(options) {
|
|
45
|
+
const profile = options.layoutProfile ?? "ream";
|
|
46
|
+
if (profile !== "ream") return profile;
|
|
47
|
+
return options.typesetBy === "word" ? "word" : "flat";
|
|
48
|
+
}
|
|
49
|
+
var FLAT_LEADING = {
|
|
50
|
+
model: "flat",
|
|
51
|
+
markLinePt: 0
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The single line Word sets a family at, in 2048ths of the size: the ascent,
|
|
55
|
+
* the gap it adds above it, and the descent. It is the face's `hhea` line (the
|
|
56
|
+
* `OS/2` typo line where the face sets USE_TYPO_METRICS), not the `usWin` cell
|
|
57
|
+
* — Book Antiqua stands 1.206 of its size in Word, its cell is 1.243. Measured
|
|
58
|
+
* in Word for Mac on the families marked `*`; the rest read by the same rule
|
|
59
|
+
* from the faces Office ships. The layout draws most of them with a twin or a
|
|
60
|
+
* fallback whose own line differs (Caladea stands 1.15 of its size, Cambria
|
|
61
|
+
* 1.172), so a run's height comes from here, by the family it names, and the
|
|
62
|
+
* face in hand answers only for a family not listed.
|
|
63
|
+
*/
|
|
64
|
+
var WORD_LINE_METRICS = (() => {
|
|
65
|
+
const arial = [
|
|
66
|
+
1854,
|
|
67
|
+
67,
|
|
68
|
+
434
|
|
69
|
+
];
|
|
70
|
+
const times = [
|
|
71
|
+
1825,
|
|
72
|
+
87,
|
|
73
|
+
443
|
|
74
|
+
];
|
|
75
|
+
const courier = [
|
|
76
|
+
1705,
|
|
77
|
+
0,
|
|
78
|
+
615
|
|
79
|
+
];
|
|
80
|
+
const aptos = [
|
|
81
|
+
1923,
|
|
82
|
+
0,
|
|
83
|
+
577
|
|
84
|
+
];
|
|
85
|
+
const franklin = [
|
|
86
|
+
1877,
|
|
87
|
+
0,
|
|
88
|
+
445
|
|
89
|
+
];
|
|
90
|
+
const century = [
|
|
91
|
+
2019,
|
|
92
|
+
0,
|
|
93
|
+
443
|
|
94
|
+
];
|
|
95
|
+
const lucida = [
|
|
96
|
+
1980,
|
|
97
|
+
0,
|
|
98
|
+
432
|
|
99
|
+
];
|
|
100
|
+
return new Map([
|
|
101
|
+
["calibri", [
|
|
102
|
+
1950,
|
|
103
|
+
0,
|
|
104
|
+
550
|
|
105
|
+
]],
|
|
106
|
+
["calibri light", [
|
|
107
|
+
1950,
|
|
108
|
+
0,
|
|
109
|
+
550
|
|
110
|
+
]],
|
|
111
|
+
["carlito", [
|
|
112
|
+
1950,
|
|
113
|
+
0,
|
|
114
|
+
550
|
|
115
|
+
]],
|
|
116
|
+
["cambria", [
|
|
117
|
+
1946,
|
|
118
|
+
0,
|
|
119
|
+
455
|
|
120
|
+
]],
|
|
121
|
+
["arial", arial],
|
|
122
|
+
["helvetica", arial],
|
|
123
|
+
["liberation sans", arial],
|
|
124
|
+
["arimo", arial],
|
|
125
|
+
["arial narrow", [
|
|
126
|
+
1916,
|
|
127
|
+
0,
|
|
128
|
+
434
|
|
129
|
+
]],
|
|
130
|
+
["arial black", [
|
|
131
|
+
2254,
|
|
132
|
+
0,
|
|
133
|
+
634
|
|
134
|
+
]],
|
|
135
|
+
["times new roman", times],
|
|
136
|
+
["times", times],
|
|
137
|
+
["liberation serif", times],
|
|
138
|
+
["tinos", times],
|
|
139
|
+
["courier new", courier],
|
|
140
|
+
["courier", courier],
|
|
141
|
+
["liberation mono", courier],
|
|
142
|
+
["cousine", courier],
|
|
143
|
+
["aptos", aptos],
|
|
144
|
+
["aptos narrow", aptos],
|
|
145
|
+
["aptos light", aptos],
|
|
146
|
+
["aptos semibold", aptos],
|
|
147
|
+
["aptos extrabold", aptos],
|
|
148
|
+
["aptos black", aptos],
|
|
149
|
+
["georgia", [
|
|
150
|
+
1878,
|
|
151
|
+
0,
|
|
152
|
+
449
|
|
153
|
+
]],
|
|
154
|
+
["verdana", [
|
|
155
|
+
2059,
|
|
156
|
+
0,
|
|
157
|
+
430
|
|
158
|
+
]],
|
|
159
|
+
["tahoma", [
|
|
160
|
+
2049,
|
|
161
|
+
0,
|
|
162
|
+
423
|
|
163
|
+
]],
|
|
164
|
+
["trebuchet ms", [
|
|
165
|
+
1923,
|
|
166
|
+
0,
|
|
167
|
+
455
|
|
168
|
+
]],
|
|
169
|
+
["candara", [
|
|
170
|
+
1484,
|
|
171
|
+
452,
|
|
172
|
+
564
|
|
173
|
+
]],
|
|
174
|
+
["consolas", [
|
|
175
|
+
1521,
|
|
176
|
+
350,
|
|
177
|
+
527
|
|
178
|
+
]],
|
|
179
|
+
["constantia", [
|
|
180
|
+
1538,
|
|
181
|
+
452,
|
|
182
|
+
510
|
|
183
|
+
]],
|
|
184
|
+
["corbel", [
|
|
185
|
+
1523,
|
|
186
|
+
425,
|
|
187
|
+
525
|
|
188
|
+
]],
|
|
189
|
+
["century gothic", [
|
|
190
|
+
2060,
|
|
191
|
+
0,
|
|
192
|
+
451
|
|
193
|
+
]],
|
|
194
|
+
["century", century],
|
|
195
|
+
["century schoolbook", century],
|
|
196
|
+
["garamond", [
|
|
197
|
+
1765,
|
|
198
|
+
0,
|
|
199
|
+
539
|
|
200
|
+
]],
|
|
201
|
+
["book antiqua", [
|
|
202
|
+
1891,
|
|
203
|
+
0,
|
|
204
|
+
578
|
|
205
|
+
]],
|
|
206
|
+
["palatino linotype", [
|
|
207
|
+
2150,
|
|
208
|
+
0,
|
|
209
|
+
613
|
|
210
|
+
]],
|
|
211
|
+
["bookman old style", [
|
|
212
|
+
1929,
|
|
213
|
+
0,
|
|
214
|
+
475
|
|
215
|
+
]],
|
|
216
|
+
["rockwell", [
|
|
217
|
+
1937,
|
|
218
|
+
0,
|
|
219
|
+
468
|
|
220
|
+
]],
|
|
221
|
+
["franklin gothic book", franklin],
|
|
222
|
+
["franklin gothic medium", franklin],
|
|
223
|
+
["franklin gothic demi", franklin],
|
|
224
|
+
["gill sans mt", [
|
|
225
|
+
1903,
|
|
226
|
+
0,
|
|
227
|
+
472
|
|
228
|
+
]],
|
|
229
|
+
["tw cen mt", [
|
|
230
|
+
1753,
|
|
231
|
+
0,
|
|
232
|
+
477
|
|
233
|
+
]],
|
|
234
|
+
["lucida sans unicode", [
|
|
235
|
+
2246,
|
|
236
|
+
0,
|
|
237
|
+
901
|
|
238
|
+
]],
|
|
239
|
+
["lucida sans", lucida],
|
|
240
|
+
["lucida bright", lucida],
|
|
241
|
+
["lucida console", [
|
|
242
|
+
1616,
|
|
243
|
+
0,
|
|
244
|
+
432
|
|
245
|
+
]],
|
|
246
|
+
["comic sans ms", [
|
|
247
|
+
2257,
|
|
248
|
+
0,
|
|
249
|
+
597
|
|
250
|
+
]],
|
|
251
|
+
["impact", [
|
|
252
|
+
2066,
|
|
253
|
+
0,
|
|
254
|
+
432
|
|
255
|
+
]],
|
|
256
|
+
["goudy old style", [
|
|
257
|
+
1832,
|
|
258
|
+
140,
|
|
259
|
+
486
|
|
260
|
+
]],
|
|
261
|
+
["calisto mt", [
|
|
262
|
+
1894,
|
|
263
|
+
0,
|
|
264
|
+
470
|
|
265
|
+
]],
|
|
266
|
+
["perpetua", [
|
|
267
|
+
1679,
|
|
268
|
+
0,
|
|
269
|
+
668
|
|
270
|
+
]],
|
|
271
|
+
["symbol", [
|
|
272
|
+
2059,
|
|
273
|
+
0,
|
|
274
|
+
450
|
|
275
|
+
]],
|
|
276
|
+
["wingdings", [
|
|
277
|
+
1841,
|
|
278
|
+
0,
|
|
279
|
+
432
|
|
280
|
+
]],
|
|
281
|
+
["wingdings 2", [
|
|
282
|
+
1727,
|
|
283
|
+
0,
|
|
284
|
+
432
|
|
285
|
+
]],
|
|
286
|
+
["wingdings 3", [
|
|
287
|
+
1900,
|
|
288
|
+
0,
|
|
289
|
+
432
|
|
290
|
+
]],
|
|
291
|
+
["webdings", [
|
|
292
|
+
1638,
|
|
293
|
+
0,
|
|
294
|
+
410
|
|
295
|
+
]]
|
|
296
|
+
]);
|
|
297
|
+
})();
|
|
42
298
|
/** §17.6.4 — how heavy Word draws the rule between columns. */
|
|
43
299
|
var COLUMN_SEPARATOR_PT = .5;
|
|
44
300
|
/**
|
|
@@ -55,7 +311,6 @@ var FOOTNOTE_RULE_PT = .75;
|
|
|
55
311
|
var FOOTNOTE_RULE_GAP_ABOVE = 4;
|
|
56
312
|
var FOOTNOTE_SEPARATOR_HEIGHT = 10;
|
|
57
313
|
var LINE_NUMBER_GAP_PT = 18;
|
|
58
|
-
var FLOAT_TEXT_GAP = 6;
|
|
59
314
|
var MIN_WRAP_WIDTH = 36;
|
|
60
315
|
function isOutOfFlowFloat(f) {
|
|
61
316
|
return f !== void 0 && f.wrap !== "topAndBottom";
|
|
@@ -315,6 +570,7 @@ function layoutStyledDocument(body, options) {
|
|
|
315
570
|
const blocks = numberedBody.map((el, idx) => {
|
|
316
571
|
while (sectionIdx < sectionCtxs.length - 1 && idx >= sectionCtxs[sectionIdx].endIndex) sectionIdx++;
|
|
317
572
|
const ctx = sectionCtxs[sectionIdx];
|
|
573
|
+
const own = ctx.sheet ? floatIntoFrame(el, ctx.sheet) : el;
|
|
318
574
|
const width = ctx.columns ? ctx.columns[0].widthPt : ctx.contentWidth;
|
|
319
575
|
const box = {
|
|
320
576
|
pageWidthPt: ctx.pageWidth,
|
|
@@ -327,7 +583,7 @@ function layoutStyledDocument(body, options) {
|
|
|
327
583
|
const group = bodyFrames.starts.get(idx);
|
|
328
584
|
if (group) return layoutFrameBlock(group, ctx.options ?? options, fontResources, imageResources, width);
|
|
329
585
|
if (bodyFrames.continued.has(idx)) return emptyBlock(options);
|
|
330
|
-
return layoutBodyElement(
|
|
586
|
+
return layoutBodyElement(own, ctx.options ?? options, fontResources, imageResources, width, ctx.pageContentHeight, box);
|
|
331
587
|
});
|
|
332
588
|
for (let i = 0; i < blocks.length; i++) {
|
|
333
589
|
const here = blocks[i];
|
|
@@ -359,9 +615,15 @@ function layoutStyledDocument(body, options) {
|
|
|
359
615
|
let laid = void 0;
|
|
360
616
|
if (content && n !== void 0 && !noteAssigned.deferredFootnotes.includes(id)) {
|
|
361
617
|
const noteBlocks = substituteNoteNumber(content, n).map((el) => layoutBodyElement(el, sectionCtx.options ?? options, fontResources, imageResources, sectionCtx.contentWidth, sectionCtx.pageContentHeight));
|
|
618
|
+
const spacing = new SpacingStack(collapsesSpacing(sectionCtx.options ?? options));
|
|
362
619
|
laid = {
|
|
363
620
|
blocks: noteBlocks,
|
|
364
|
-
heightPt: noteBlocks.reduce((sum, b) =>
|
|
621
|
+
heightPt: noteBlocks.reduce((sum, b) => {
|
|
622
|
+
if (b.kind !== "paragraph") return sum;
|
|
623
|
+
const before = spacing.open(b.spacingBeforePt);
|
|
624
|
+
spacing.close(b.spacingAfterPt);
|
|
625
|
+
return sum + before + b.heightPt + b.spacingAfterPt;
|
|
626
|
+
}, 0)
|
|
365
627
|
};
|
|
366
628
|
}
|
|
367
629
|
byId.set(id, laid);
|
|
@@ -435,7 +697,7 @@ function layoutStyledDocument(body, options) {
|
|
|
435
697
|
const commentNotes = options.commentAnnotations && options.comments ? buildCommentNotes(noteAssigned.comments, options.comments) : void 0;
|
|
436
698
|
const reflowParagraph = (paragraph, width, widths) => layoutParagraphBlock(paragraph, options, fontResources, imageResources, width, widths);
|
|
437
699
|
return {
|
|
438
|
-
pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph, backgroundImageOf(options, imageResources)),
|
|
700
|
+
pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph, backgroundImageOf(options, imageResources), collapsesSpacing(options), options.compatibilityMode),
|
|
439
701
|
resources: options.resources ?? new ResourceStore(),
|
|
440
702
|
fontResources,
|
|
441
703
|
imageResources,
|
|
@@ -509,9 +771,52 @@ function buildSectionContext(section, options, headersFooters, fontResources, im
|
|
|
509
771
|
bottom: dims.marginBottom
|
|
510
772
|
}, relativeBox);
|
|
511
773
|
const columns = buildColumnGeometry(section.properties.columns, contentWidth);
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
774
|
+
const bodyMarginsFor = (band) => {
|
|
775
|
+
const header = pickBand(headerSet, band).heightPt ?? 0;
|
|
776
|
+
const footer = pickBand(footerSet, band).heightPt ?? 0;
|
|
777
|
+
return {
|
|
778
|
+
top: Math.max(dims.marginTop, header > 0 ? dims.headerOffsetPt + header : 0),
|
|
779
|
+
bottom: Math.max(dims.marginBottom, footer > 0 ? dims.footerOffsetPt + footer : 0)
|
|
780
|
+
};
|
|
781
|
+
};
|
|
782
|
+
const bandMargins = {
|
|
783
|
+
default: bodyMarginsFor("default"),
|
|
784
|
+
first: bodyMarginsFor("first"),
|
|
785
|
+
even: bodyMarginsFor("even")
|
|
786
|
+
};
|
|
787
|
+
const marginTop = bandMargins.default.top;
|
|
788
|
+
const marginBottom = bandMargins.default.bottom;
|
|
789
|
+
if (section.properties.textDirection !== void 0) {
|
|
790
|
+
const sheet = {
|
|
791
|
+
width: dims.pageWidth,
|
|
792
|
+
height: dims.pageHeight,
|
|
793
|
+
marginLeft: dims.marginLeft,
|
|
794
|
+
marginRight: dims.marginRight,
|
|
795
|
+
marginTop,
|
|
796
|
+
marginBottom
|
|
797
|
+
};
|
|
798
|
+
const across = sheet.height - sheet.marginTop - sheet.marginBottom;
|
|
799
|
+
const turnedColumns = buildColumnGeometry(section.properties.columns, across);
|
|
800
|
+
return {
|
|
801
|
+
endIndex: section.endIndex,
|
|
802
|
+
properties: section.properties,
|
|
803
|
+
pageWidth: sheet.height,
|
|
804
|
+
pageHeight: sheet.width,
|
|
805
|
+
marginLeft: sheet.marginTop,
|
|
806
|
+
marginTop: sheet.marginRight,
|
|
807
|
+
marginBottom: sheet.marginLeft,
|
|
808
|
+
contentWidth: across,
|
|
809
|
+
pageContentHeight: sheet.width - sheet.marginRight - sheet.marginLeft,
|
|
810
|
+
...turnedColumns ? { columns: turnedColumns } : {},
|
|
811
|
+
headerSet,
|
|
812
|
+
footerSet,
|
|
813
|
+
titlePg: section.properties.titlePg === true,
|
|
814
|
+
evenAndOddHeaders: section.properties.evenAndOddHeaders === true,
|
|
815
|
+
continuous: section.properties.sectionStart === "continuous",
|
|
816
|
+
options: secOptions,
|
|
817
|
+
sheet
|
|
818
|
+
};
|
|
819
|
+
}
|
|
515
820
|
return {
|
|
516
821
|
endIndex: section.endIndex,
|
|
517
822
|
properties: section.properties,
|
|
@@ -519,12 +824,13 @@ function buildSectionContext(section, options, headersFooters, fontResources, im
|
|
|
519
824
|
pageHeight: dims.pageHeight,
|
|
520
825
|
marginLeft: dims.marginLeft,
|
|
521
826
|
marginTop,
|
|
522
|
-
marginBottom
|
|
827
|
+
marginBottom,
|
|
523
828
|
contentWidth,
|
|
524
|
-
pageContentHeight: dims.pageHeight - marginTop -
|
|
829
|
+
pageContentHeight: dims.pageHeight - marginTop - marginBottom,
|
|
525
830
|
...columns ? { columns } : {},
|
|
526
831
|
headerSet,
|
|
527
832
|
footerSet,
|
|
833
|
+
bandMargins,
|
|
528
834
|
titlePg: section.properties.titlePg === true,
|
|
529
835
|
evenAndOddHeaders: section.properties.evenAndOddHeaders === true,
|
|
530
836
|
continuous: section.properties.sectionStart === "continuous",
|
|
@@ -572,15 +878,15 @@ function layoutHeaderSet(section, headersFooters, options, fontResources, imageR
|
|
|
572
878
|
let measured = 0;
|
|
573
879
|
const render = (c) => {
|
|
574
880
|
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth, imageResources, relativeBox);
|
|
575
|
-
measured = blocksHeight(blocks);
|
|
576
|
-
return markPagination(drawBlocksSequentially(blocks, marginLeft, pageHeight - headerOffsetPt, pageHeight, contentWidth, void 0, bandMargins));
|
|
881
|
+
measured = blocksHeight(blocks, collapsesSpacing(options));
|
|
882
|
+
return markPagination(drawBlocksSequentially(blocks, marginLeft, pageHeight - headerOffsetPt, pageHeight, contentWidth, void 0, bandMargins, collapsesSpacing(options)));
|
|
577
883
|
};
|
|
578
884
|
if (contentHasPageFields(content)) {
|
|
579
|
-
render(substitutePageFields(content, 1, 1));
|
|
885
|
+
render(substitutePageFields(content, "1", 1));
|
|
580
886
|
return {
|
|
581
887
|
commands: [],
|
|
582
888
|
heightPt: measured,
|
|
583
|
-
renderDynamic: (
|
|
889
|
+
renderDynamic: (page, total) => render(substitutePageFields(content, page, total))
|
|
584
890
|
};
|
|
585
891
|
}
|
|
586
892
|
return {
|
|
@@ -609,11 +915,61 @@ function balancedColumnHeight(bandHeightPt, colCount, roomPt) {
|
|
|
609
915
|
if (bandHeightPt > roomPt * colCount) return 0;
|
|
610
916
|
return Math.min(roomPt, bandHeightPt / colCount);
|
|
611
917
|
}
|
|
612
|
-
/**
|
|
613
|
-
|
|
918
|
+
/**
|
|
919
|
+
* §17.3.1.33 — the space between paragraphs down one story (a page, a cell, a
|
|
920
|
+
* band, a box). Where the document is set as Word sets it, two paragraphs
|
|
921
|
+
* stand the LARGER of the first's space after and the second's space before
|
|
922
|
+
* apart, not the two together. Measured in Word for Mac, in modes 14 and 15
|
|
923
|
+
* alike: 10pt after and 10pt before stand 10pt apart; 10 and 24 stand 24,
|
|
924
|
+
* whichever comes first; 6 and 12 stand 12 either way. An empty paragraph
|
|
925
|
+
* between takes its share of both sides. Elsewhere the two add up.
|
|
926
|
+
*/
|
|
927
|
+
var SpacingStack = class {
|
|
928
|
+
/** The space after the last block, which a paragraph right below may share. */
|
|
929
|
+
shared = 0;
|
|
930
|
+
constructor(collapse) {
|
|
931
|
+
this.collapse = collapse;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* @param beforePt A block's own space before.
|
|
935
|
+
* @returns The room to open above it.
|
|
936
|
+
*/
|
|
937
|
+
open(beforePt) {
|
|
938
|
+
const room = this.collapse ? Math.max(0, beforePt - this.shared) : beforePt;
|
|
939
|
+
this.shared = 0;
|
|
940
|
+
return room;
|
|
941
|
+
}
|
|
942
|
+
/** @param spacingPt The space a block just left below itself, as far as a paragraph may share it. */
|
|
943
|
+
close(spacingPt) {
|
|
944
|
+
this.shared = spacingPt;
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
/** Whether a layout stacks paragraph spacing as Word does (see {@link SpacingStack}). */
|
|
948
|
+
function collapsesSpacing(options) {
|
|
949
|
+
return options.typesetBy === "word";
|
|
950
|
+
}
|
|
951
|
+
/** A block's space after, less the line a picture's spacing adds under it: the part a paragraph below shares. */
|
|
952
|
+
function sharedAfter(block) {
|
|
953
|
+
return block.spacingAfterPt - (block.lineExtraPt ?? 0);
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Total laid-out height of a run of blocks, paragraph spacing included.
|
|
957
|
+
*
|
|
958
|
+
* @param blocks The blocks, in order.
|
|
959
|
+
* @param collapse Whether their spacing stacks as Word stacks it (see {@link SpacingStack}).
|
|
960
|
+
* @returns The height in points.
|
|
961
|
+
*/
|
|
962
|
+
function blocksHeight(blocks, collapse = false) {
|
|
963
|
+
const spacing = new SpacingStack(collapse);
|
|
614
964
|
return blocks.reduce((sum, b) => {
|
|
615
965
|
if (b.kind !== "paragraph" && isOutOfFlowFloat(b.float)) return sum;
|
|
616
|
-
|
|
966
|
+
if (!("spacingBeforePt" in b)) {
|
|
967
|
+
spacing.close(0);
|
|
968
|
+
return sum + b.heightPt;
|
|
969
|
+
}
|
|
970
|
+
const before = spacing.open(b.spacingBeforePt);
|
|
971
|
+
spacing.close(sharedAfter(b));
|
|
972
|
+
return sum + before + b.heightPt + b.spacingAfterPt;
|
|
617
973
|
}, 0);
|
|
618
974
|
}
|
|
619
975
|
function layoutFooterSet(section, headersFooters, options, fontResources, imageResources, contentWidth, marginLeft, pageHeight, footerOffsetPt, bandMargins, relativeBox) {
|
|
@@ -622,15 +978,25 @@ function layoutFooterSet(section, headersFooters, options, fontResources, imageR
|
|
|
622
978
|
if (!ref) return { commands: [] };
|
|
623
979
|
const content = headersFooters.get(ref.relationshipId);
|
|
624
980
|
if (!content) return { commands: [] };
|
|
981
|
+
let measured = 0;
|
|
625
982
|
const render = (c) => {
|
|
626
983
|
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth, imageResources, relativeBox);
|
|
627
|
-
|
|
984
|
+
const totalHeight = blocksHeight(blocks, collapsesSpacing(options));
|
|
985
|
+
measured = totalHeight;
|
|
986
|
+
return markPagination(drawBlocksSequentially(blocks, marginLeft, footerOffsetPt + totalHeight, pageHeight, contentWidth, void 0, bandMargins, collapsesSpacing(options)));
|
|
628
987
|
};
|
|
629
|
-
if (contentHasPageFields(content))
|
|
630
|
-
|
|
631
|
-
|
|
988
|
+
if (contentHasPageFields(content)) {
|
|
989
|
+
render(substitutePageFields(content, "1", 1));
|
|
990
|
+
return {
|
|
991
|
+
commands: [],
|
|
992
|
+
heightPt: measured,
|
|
993
|
+
renderDynamic: (page, total) => render(substitutePageFields(content, page, total))
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
commands: render(content),
|
|
998
|
+
heightPt: measured
|
|
632
999
|
};
|
|
633
|
-
return { commands: render(content) };
|
|
634
1000
|
};
|
|
635
1001
|
return {
|
|
636
1002
|
default: band("default"),
|
|
@@ -642,7 +1008,20 @@ function contentHasPageFields(content) {
|
|
|
642
1008
|
for (const el of content) if (el.kind === "paragraph" && el.paragraph.runs.some((r) => r.field !== void 0)) return true;
|
|
643
1009
|
return false;
|
|
644
1010
|
}
|
|
645
|
-
|
|
1011
|
+
/**
|
|
1012
|
+
* §17.6.12 — a page's number as its section prints it: in the numerals
|
|
1013
|
+
* `w:pgNumType w:fmt` names (§17.18.59), decimal where it names none. A format
|
|
1014
|
+
* that has no numeral for the number — a roman zero — prints it in digits.
|
|
1015
|
+
*
|
|
1016
|
+
* @param pageNumber The page's number in its section's count.
|
|
1017
|
+
* @param format The section's page-number format.
|
|
1018
|
+
* @returns The text a PAGE field shows on that page.
|
|
1019
|
+
*/
|
|
1020
|
+
function pageNumberText(pageNumber, format) {
|
|
1021
|
+
const text = format === void 0 ? "" : formatCounter(format, pageNumber);
|
|
1022
|
+
return text !== "" ? text : String(pageNumber);
|
|
1023
|
+
}
|
|
1024
|
+
function substitutePageFields(content, pageText, totalPages) {
|
|
646
1025
|
return content.map((el) => {
|
|
647
1026
|
if (el.kind !== "paragraph") return el;
|
|
648
1027
|
if (!el.paragraph.runs.some((r) => r.field !== void 0)) return el;
|
|
@@ -652,7 +1031,7 @@ function substitutePageFields(content, pageNumber, totalPages) {
|
|
|
652
1031
|
...el.paragraph,
|
|
653
1032
|
runs: el.paragraph.runs.map((r) => r.field === void 0 ? r : {
|
|
654
1033
|
...r,
|
|
655
|
-
text:
|
|
1034
|
+
text: r.field === "PAGE" ? pageText : String(totalPages)
|
|
656
1035
|
})
|
|
657
1036
|
}
|
|
658
1037
|
};
|
|
@@ -953,12 +1332,33 @@ function layoutBodyElement(el, options, fontResources, imageResources, contentWi
|
|
|
953
1332
|
if (el.kind === "image") {
|
|
954
1333
|
const res = el.image.resource ? imageResources?.get(el.image.resource) : void 0;
|
|
955
1334
|
const meta = res?.metafile;
|
|
956
|
-
return meta ? layoutMetafileBlock(el.image, meta, res.metafileImages, options, fontResources, contentWidth, maxHeight, box) : layoutImageBlock(el.image, imageResources, contentWidth, maxHeight, box);
|
|
1335
|
+
return meta ? layoutMetafileBlock(el.image, meta, res.metafileImages, options, fontResources, contentWidth, maxHeight, box) : layoutImageBlock(el.image, imageResources, contentWidth, maxHeight, box, options);
|
|
957
1336
|
}
|
|
958
1337
|
if (el.kind === "chart") return layoutChartBlock(el.chart, options, fontResources, contentWidth, maxHeight, imageResources);
|
|
959
1338
|
return layoutShapeBlock(el.shape, options, fontResources, imageResources, contentWidth, maxHeight, box);
|
|
960
1339
|
}
|
|
961
|
-
|
|
1340
|
+
/**
|
|
1341
|
+
* §17.3.1.33 — what a paragraph's line spacing adds under a picture that
|
|
1342
|
+
* stands on a line of its own. Word sets that line as tall as the picture,
|
|
1343
|
+
* and a spacing of more than one line adds what the extra lines of the
|
|
1344
|
+
* paragraph mark's font would, not a share of the picture: in Word for Mac a
|
|
1345
|
+
* 100pt picture's next paragraph stands 100, 102.5 and 113 points down at
|
|
1346
|
+
* single, 1.15 and double spacing of an 11pt mark.
|
|
1347
|
+
*
|
|
1348
|
+
* @param pp The paragraph the picture stands on, resolved.
|
|
1349
|
+
* @param options The render options its mark's font resolves against; absent
|
|
1350
|
+
* for a picture out of the flow.
|
|
1351
|
+
* @returns The extra points, 0 where the spacing is not a multiple above one.
|
|
1352
|
+
*/
|
|
1353
|
+
function pictureLineExtra(pp, options) {
|
|
1354
|
+
if (!options) return 0;
|
|
1355
|
+
if (pp.spacingLine === void 0 || (pp.spacingLineRule ?? "auto") !== "auto") return 0;
|
|
1356
|
+
const multiple = Math.round(pp.spacingLine * 20) / 240;
|
|
1357
|
+
if (multiple <= 1) return 0;
|
|
1358
|
+
if (leadingOf(options) !== "flat") return (multiple - 1) * paragraphLeading(pp, options).markLinePt;
|
|
1359
|
+
return (multiple - 1) * resolveRunProperties({}, pp, options.styles).fontSizePt * 1.2;
|
|
1360
|
+
}
|
|
1361
|
+
function layoutImageBlock(image, imageResources, contentWidth, maxHeight, box, options) {
|
|
962
1362
|
let widthPt = relativeWidth(image.relativeSize, contentWidth, box) ?? image.width;
|
|
963
1363
|
let heightPt = relativeHeight(image.relativeSize, maxHeight, box) ?? (image.relativeSize?.widthPct && image.width > 0 ? image.height * (widthPt / image.width) : image.height);
|
|
964
1364
|
if (widthPt > contentWidth && !image.float) {
|
|
@@ -993,6 +1393,7 @@ function layoutImageBlock(image, imageResources, contentWidth, maxHeight, box) {
|
|
|
993
1393
|
heightPt = reservedH;
|
|
994
1394
|
drawInset = box;
|
|
995
1395
|
}
|
|
1396
|
+
const lineExtraPt = image.float ? 0 : pictureLineExtra(image.paragraphProperties, options);
|
|
996
1397
|
const res = image.resource ? imageResources?.get(image.resource) : void 0;
|
|
997
1398
|
const resolvedAlignment = image.paragraphProperties.alignment ?? "left";
|
|
998
1399
|
return {
|
|
@@ -1010,7 +1411,8 @@ function layoutImageBlock(image, imageResources, contentWidth, maxHeight, box) {
|
|
|
1010
1411
|
...image.flipV ? { flipV: true } : {},
|
|
1011
1412
|
...drawInset ? { drawInset } : {},
|
|
1012
1413
|
spacingBeforePt: image.paragraphProperties.spacingBefore ?? 0,
|
|
1013
|
-
spacingAfterPt: image.paragraphProperties.spacingAfter ?? 0,
|
|
1414
|
+
spacingAfterPt: (image.paragraphProperties.spacingAfter ?? 0) + lineExtraPt,
|
|
1415
|
+
...lineExtraPt > 0 ? { lineExtraPt } : {},
|
|
1014
1416
|
...image.altText ? { altText: image.altText } : {},
|
|
1015
1417
|
...image.float ? { float: image.float } : {}
|
|
1016
1418
|
};
|
|
@@ -1054,8 +1456,10 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
1054
1456
|
const upright = text?.upright;
|
|
1055
1457
|
const warp = text?.warp;
|
|
1056
1458
|
if (text && text.content.length > 0) {
|
|
1057
|
-
const innerWidth = warp ? Number.MAX_SAFE_INTEGER : vertical ? Math.max(1, heightPt - insetTopPt - insetBottomPt) : Math.max(1, widthPt - insetLeftPt - insetRightPt);
|
|
1459
|
+
const innerWidth = warp || text.noWrap === true ? Number.MAX_SAFE_INTEGER : vertical ? Math.max(1, heightPt - insetTopPt - insetBottomPt) : Math.max(1, widthPt - insetLeftPt - insetRightPt);
|
|
1460
|
+
const spacing = new SpacingStack(collapsesSpacing(options));
|
|
1058
1461
|
for (const el of text.content) {
|
|
1462
|
+
if (el.kind !== "paragraph") spacing.close(0);
|
|
1059
1463
|
if (el.kind === "image") {
|
|
1060
1464
|
const res = el.image.resource ? imageResources?.get(el.image.resource) : void 0;
|
|
1061
1465
|
const scaled = Math.min(1, innerWidth / Math.max(1, el.image.width));
|
|
@@ -1129,7 +1533,8 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
1129
1533
|
continue;
|
|
1130
1534
|
}
|
|
1131
1535
|
const blk = layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, innerWidth);
|
|
1132
|
-
const
|
|
1536
|
+
const opened = spacing.open(blk.spacingBeforePt);
|
|
1537
|
+
const before = textLines.length > 0 ? opened : 0;
|
|
1133
1538
|
if (before > 0) {
|
|
1134
1539
|
const last = textLines.length - 1;
|
|
1135
1540
|
textLineGaps.set(last, (textLineGaps.get(last) ?? 0) + before);
|
|
@@ -1144,6 +1549,7 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
1144
1549
|
textLineGaps.set(last, (textLineGaps.get(last) ?? 0) + blk.spacingAfterPt);
|
|
1145
1550
|
}
|
|
1146
1551
|
textHeightPt += blk.spacingAfterPt;
|
|
1552
|
+
spacing.close(blk.spacingAfterPt);
|
|
1147
1553
|
}
|
|
1148
1554
|
}
|
|
1149
1555
|
if (text?.autoFit && !vertical && textLines.length > 0) heightPt = textHeightPt + insetTopPt + insetBottomPt;
|
|
@@ -1259,6 +1665,7 @@ function layoutMetafileBlock(image, pic, blitted, options, fontResources, conten
|
|
|
1259
1665
|
const heightPt = placed.heightPt;
|
|
1260
1666
|
const layout = rotateDrawing(metafileDrawing(pic, widthPt, heightPt, options, fontResources, image.colorChange, blitted), (image.rotation60k ?? 0) / 6e4, widthPt / 2, heightPt / 2);
|
|
1261
1667
|
const pp = image.paragraphProperties;
|
|
1668
|
+
const lineExtraPt = image.float ? 0 : pictureLineExtra(pp, options);
|
|
1262
1669
|
return {
|
|
1263
1670
|
kind: "chart",
|
|
1264
1671
|
widthPt,
|
|
@@ -1266,7 +1673,8 @@ function layoutMetafileBlock(image, pic, blitted, options, fontResources, conten
|
|
|
1266
1673
|
layout,
|
|
1267
1674
|
resolvedAlignment: pp.alignment ?? "left",
|
|
1268
1675
|
spacingBeforePt: pp.spacingBefore ?? 0,
|
|
1269
|
-
spacingAfterPt: pp.spacingAfter ?? 0,
|
|
1676
|
+
spacingAfterPt: (pp.spacingAfter ?? 0) + lineExtraPt,
|
|
1677
|
+
...lineExtraPt > 0 ? { lineExtraPt } : {},
|
|
1270
1678
|
figureRole: "Image",
|
|
1271
1679
|
...image.altText ? { altText: image.altText } : {},
|
|
1272
1680
|
...image.float ? { float: image.float } : {}
|
|
@@ -2231,9 +2639,10 @@ function bandAnchorTop(float, cursorY, pageHeight, heightPt = 0, margins) {
|
|
|
2231
2639
|
const offset = float.posV?.offsetPt ?? 0;
|
|
2232
2640
|
return float.posV?.relativeFrom === "page" ? pageHeight - offset : cursorY - offset;
|
|
2233
2641
|
}
|
|
2234
|
-
function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth, structId, bandMargins) {
|
|
2642
|
+
function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth, structId, bandMargins, collapse = false) {
|
|
2235
2643
|
const out = [];
|
|
2236
2644
|
let cursorY = startY;
|
|
2645
|
+
const spacing = new SpacingStack(collapse);
|
|
2237
2646
|
const isBehind = (b) => "float" in b && b.float?.behind === true;
|
|
2238
2647
|
const ordered = blocks.some(isBehind) ? [...blocks.filter(isBehind), ...blocks.filter((b) => !isBehind(b))] : blocks;
|
|
2239
2648
|
for (const block of ordered) {
|
|
@@ -2252,18 +2661,20 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth
|
|
|
2252
2661
|
emitRowChunk(out, row, tableX, cursorY, pageHeight, block.colCount);
|
|
2253
2662
|
cursorY -= row.heightPt;
|
|
2254
2663
|
}
|
|
2664
|
+
spacing.close(0);
|
|
2255
2665
|
continue;
|
|
2256
2666
|
}
|
|
2257
2667
|
if (block.kind === "chart") {
|
|
2258
|
-
cursorY -= block.spacingBeforePt + block.heightPt;
|
|
2668
|
+
cursorY -= spacing.open(block.spacingBeforePt) + block.heightPt;
|
|
2259
2669
|
const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
|
|
2260
2670
|
out.push(...chartPageItems(block, startX + offset, cursorY, pageHeight, structId));
|
|
2261
2671
|
cursorY -= block.spacingAfterPt;
|
|
2672
|
+
spacing.close(sharedAfter(block));
|
|
2262
2673
|
continue;
|
|
2263
2674
|
}
|
|
2264
2675
|
if (block.kind === "image") {
|
|
2265
|
-
cursorY -= block.spacingBeforePt;
|
|
2266
2676
|
const anchored = bandAnchor(block.float, block.widthPt, contentWidth, startX);
|
|
2677
|
+
cursorY -= anchored === void 0 ? spacing.open(block.spacingBeforePt) : block.spacingBeforePt;
|
|
2267
2678
|
const offset = anchored ?? alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
|
|
2268
2679
|
const top = anchored === void 0 || !block.float ? cursorY : bandAnchorTop(block.float, cursorY, pageHeight, block.heightPt, bandMargins);
|
|
2269
2680
|
out.push({
|
|
@@ -2283,19 +2694,21 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth
|
|
|
2283
2694
|
if (block.outline) out.push(...pictureOutlineItems(block.outline, startX + offset, pageHeight - top, block.widthPt, block.heightPt, structId));
|
|
2284
2695
|
if (anchored === void 0) cursorY -= block.heightPt;
|
|
2285
2696
|
cursorY -= block.spacingAfterPt;
|
|
2697
|
+
if (anchored === void 0) spacing.close(sharedAfter(block));
|
|
2286
2698
|
continue;
|
|
2287
2699
|
}
|
|
2288
2700
|
if (block.kind === "shape") {
|
|
2289
|
-
cursorY -= block.spacingBeforePt;
|
|
2290
2701
|
const anchored = bandAnchor(block.float, block.widthPt, contentWidth, startX);
|
|
2702
|
+
cursorY -= anchored === void 0 ? spacing.open(block.spacingBeforePt) : block.spacingBeforePt;
|
|
2291
2703
|
const offset = anchored ?? alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
|
|
2292
2704
|
const top = anchored === void 0 || !block.float ? cursorY : bandAnchorTop(block.float, cursorY, pageHeight, block.heightPt, bandMargins);
|
|
2293
2705
|
emitShapeItems(block, startX + offset, top - block.heightPt, out, pageHeight, structId);
|
|
2294
2706
|
if (anchored === void 0) cursorY -= block.heightPt;
|
|
2295
2707
|
cursorY -= block.spacingAfterPt;
|
|
2708
|
+
if (anchored === void 0) spacing.close(block.spacingAfterPt);
|
|
2296
2709
|
continue;
|
|
2297
2710
|
}
|
|
2298
|
-
cursorY -= block.spacingBeforePt;
|
|
2711
|
+
cursorY -= spacing.open(block.spacingBeforePt);
|
|
2299
2712
|
const bandTopY = cursorY;
|
|
2300
2713
|
for (const line of block.lines) {
|
|
2301
2714
|
const h = computeLineHeight(line, block.resolved);
|
|
@@ -2312,6 +2725,7 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth
|
|
|
2312
2725
|
}
|
|
2313
2726
|
out.push(...paragraphDecoration(block.resolved, startX + block.resolved.indentLeft, startX + contentWidth - block.resolved.indentRight, bandTopY, cursorY, pageHeight));
|
|
2314
2727
|
cursorY -= block.spacingAfterPt;
|
|
2728
|
+
spacing.close(block.spacingAfterPt);
|
|
2315
2729
|
}
|
|
2316
2730
|
return out;
|
|
2317
2731
|
}
|
|
@@ -2604,6 +3018,7 @@ function lookupFont(resources, fontKey) {
|
|
|
2604
3018
|
}
|
|
2605
3019
|
function collectFontResources(body, headersFooters, options, imageResources) {
|
|
2606
3020
|
const used = /* @__PURE__ */ new Map();
|
|
3021
|
+
const numerals = [...new Set([options.section, ...(options.sections ?? []).map((sec) => sec.properties)].flatMap((p) => p?.pageNumberFormat !== void 0 ? [p.pageNumberFormat] : []))].map((fmt) => Array.from({ length: 200 }, (_, i) => formatCounter(fmt, i + 1)).join("")).join("");
|
|
2607
3022
|
const addRun = (run, para) => {
|
|
2608
3023
|
const resolved = resolveRunProperties(run.properties, para.properties, options.styles);
|
|
2609
3024
|
const { fontKey, parsed } = runFontKeyAndParsed(options, resolved.fontFamily.ascii, resolved.bold, resolved.italic);
|
|
@@ -2678,7 +3093,7 @@ function collectFontResources(body, headersFooters, options, imageResources) {
|
|
|
2678
3093
|
if (run.inlineImage && !run.text) continue;
|
|
2679
3094
|
if (run.field !== void 0) {
|
|
2680
3095
|
addRun({
|
|
2681
|
-
text: `${run.text}0123456789`,
|
|
3096
|
+
text: `${run.text}0123456789${numerals}`,
|
|
2682
3097
|
properties: run.properties
|
|
2683
3098
|
}, el.paragraph);
|
|
2684
3099
|
continue;
|
|
@@ -2793,13 +3208,10 @@ function collectFontResources(body, headersFooters, options, imageResources) {
|
|
|
2793
3208
|
* the paragraph mark's own font (§17.3.1.31 `w:rPr` on the `w:pPr`, which is
|
|
2794
3209
|
* exactly the run properties Word keeps for a paragraph that holds no runs).
|
|
2795
3210
|
*/
|
|
2796
|
-
function emptyLine(paragraph, options, resolved, availableWidthPt
|
|
2797
|
-
const
|
|
2798
|
-
const
|
|
2799
|
-
const
|
|
2800
|
-
const { variant } = options.registry.resolveByStyle(mark.bold, mark.italic);
|
|
2801
|
-
const parsed = fontResources.get(variant)?.parsed;
|
|
2802
|
-
const metric = profile !== "ream" && parsed ? fontLeadingPt(parsed, fontSizePt, profile) : void 0;
|
|
3211
|
+
function emptyLine(paragraph, options, resolved, availableWidthPt) {
|
|
3212
|
+
const fontSizePt = resolveRunProperties(paragraph.properties.runProperties ?? {}, paragraph.properties, options.styles).fontSizePt;
|
|
3213
|
+
const model = leadingOf(options);
|
|
3214
|
+
const metric = model === "flat" ? void 0 : markLine(paragraph.properties, options, model);
|
|
2803
3215
|
return {
|
|
2804
3216
|
tokens: [],
|
|
2805
3217
|
contentWidthPt: 0,
|
|
@@ -2810,10 +3222,37 @@ function emptyLine(paragraph, options, resolved, availableWidthPt, fontResources
|
|
|
2810
3222
|
isLastInParagraph: true,
|
|
2811
3223
|
...metric && metric.ascentPt + metric.descentPt > 0 ? {
|
|
2812
3224
|
metricHeightPt: metric.ascentPt + metric.descentPt + metric.lineGapPt,
|
|
2813
|
-
metricDescentPt: metric.descentPt
|
|
3225
|
+
metricDescentPt: metric.descentPt,
|
|
3226
|
+
metricTextHeightPt: metric.ascentPt + metric.descentPt + metric.lineGapPt
|
|
2814
3227
|
} : {}
|
|
2815
3228
|
};
|
|
2816
3229
|
}
|
|
3230
|
+
/**
|
|
3231
|
+
* The single line of a paragraph mark's font (§17.3.1.29 `w:rPr` on the
|
|
3232
|
+
* `w:pPr`) under a model that reads faces: what an empty paragraph stands, and
|
|
3233
|
+
* what a spacing of more lines adds to a line with no text on it.
|
|
3234
|
+
*
|
|
3235
|
+
* @param pp The paragraph's properties.
|
|
3236
|
+
* @param options Render options (styles, fonts).
|
|
3237
|
+
* @param model The line model.
|
|
3238
|
+
* @returns Its ascent, descent and gap, in points.
|
|
3239
|
+
*/
|
|
3240
|
+
function markLine(pp, options, model) {
|
|
3241
|
+
const mark = resolveRunProperties(pp.runProperties ?? {}, pp, options.styles);
|
|
3242
|
+
const family = mark.fontFamily.ascii;
|
|
3243
|
+
const { parsed } = runFontKeyAndParsed(options, family, mark.bold, mark.italic);
|
|
3244
|
+
return fontLeadingPt(parsed, mark.fontSizePt, model, family);
|
|
3245
|
+
}
|
|
3246
|
+
/** What a paragraph's lines are reckoned from, under the layout's model. */
|
|
3247
|
+
function paragraphLeading(pp, options) {
|
|
3248
|
+
const model = leadingOf(options);
|
|
3249
|
+
if (model === "flat") return FLAT_LEADING;
|
|
3250
|
+
const m = markLine(pp, options, model);
|
|
3251
|
+
return {
|
|
3252
|
+
model,
|
|
3253
|
+
markLinePt: m.ascentPt + m.descentPt + m.lineGapPt
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
2817
3256
|
function layoutParagraphBlock(paragraph, options, fontResources, imageResources, contentWidth, lineWidths) {
|
|
2818
3257
|
const cascaded = resolveParagraphProperties(paragraph.properties, options.styles);
|
|
2819
3258
|
const baseResolved = options.gridLinePitchPt !== void 0 && cascaded.snapToGrid !== false ? {
|
|
@@ -2842,7 +3281,8 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
|
|
|
2842
3281
|
const tokens = tokenizeParagraph(paragraph, options, fontResources, imageResources, contentWidth, baseDir);
|
|
2843
3282
|
const firstLineWidth = paragraphMaxWidth(resolved, contentWidth, true);
|
|
2844
3283
|
const otherWidth = paragraphMaxWidth(resolved, contentWidth, false);
|
|
2845
|
-
const
|
|
3284
|
+
const leading = paragraphLeading(paragraph.properties, options);
|
|
3285
|
+
const runWrap = (part, firstWidth, widths) => wrap(part, firstWidth, otherWidth, resolved, options.hyphenator, options.layoutProfile ?? "ream", leading, options.doNotExpandShiftReturn === true, widths);
|
|
2846
3286
|
const parts = splitAtColumnBreaks(tokens);
|
|
2847
3287
|
const columnBreakLines = /* @__PURE__ */ new Set();
|
|
2848
3288
|
let wrapped;
|
|
@@ -2858,12 +3298,13 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
|
|
|
2858
3298
|
})));
|
|
2859
3299
|
}
|
|
2860
3300
|
}
|
|
2861
|
-
const lines = wrapped.length > 0 ? wrapped : [emptyLine(paragraph, options, resolved, Math.max(firstLineWidth, 0)
|
|
3301
|
+
const lines = wrapped.length > 0 ? wrapped : [emptyLine(paragraph, options, resolved, Math.max(firstLineWidth, 0))];
|
|
2862
3302
|
let heightPt = 0;
|
|
2863
3303
|
for (const line of lines) heightPt += computeLineHeight(line, resolved);
|
|
2864
3304
|
const numbering = paragraph.properties.numbering;
|
|
2865
3305
|
const breakIdx = paragraph.runs.findIndex((r) => r.pageBreak === true);
|
|
2866
|
-
const
|
|
3306
|
+
const holds = (r) => r.text !== "" || r.inlineImage !== void 0 || r.math !== void 0;
|
|
3307
|
+
const leadingPageBreak = breakIdx >= 0 && paragraph.runs.slice(0, breakIdx + 1).every((r) => r.text === "" && r.inlineImage === void 0) && paragraph.runs.slice(breakIdx + 1).some(holds);
|
|
2867
3308
|
const trailingPageBreak = paragraph.runs.some((r, i) => r.pageBreak === true && (!leadingPageBreak || i > breakIdx));
|
|
2868
3309
|
return {
|
|
2869
3310
|
kind: "paragraph",
|
|
@@ -3186,7 +3627,7 @@ function measureRunText(plan, text, face) {
|
|
|
3186
3627
|
font: plan.font,
|
|
3187
3628
|
...plan.synthetic ? { synthetic: plan.synthetic } : {}
|
|
3188
3629
|
};
|
|
3189
|
-
const base = drawn.font.measure.textWidthPt(text, plan.fontSizePt) * (drawn.synthetic?.widthScale ?? 1);
|
|
3630
|
+
const base = drawn.font.measure.textWidthPt(text, plan.fontSizePt) * (drawn.synthetic?.widthScale ?? 1) * (plan.resolvedRun.widthScale ?? 1);
|
|
3190
3631
|
const extra = plan.resolvedRun.letterSpacingPt;
|
|
3191
3632
|
return extra === void 0 || extra === 0 ? base : base + extra * [...text].length;
|
|
3192
3633
|
}
|
|
@@ -3634,7 +4075,7 @@ function splitToWidth(tok, widthPt) {
|
|
|
3634
4075
|
}
|
|
3635
4076
|
/** The fewest characters a mid-word break may leave on a line. */
|
|
3636
4077
|
var MIN_SPLIT_CHARS = 6;
|
|
3637
|
-
function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved,
|
|
4078
|
+
function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved, leading, allowEmpty = false) {
|
|
3638
4079
|
let st = start;
|
|
3639
4080
|
let et = breakIdx;
|
|
3640
4081
|
while (st < et && (entries[st].token === null || !isFirst && entries[st].token?.isSpace === true)) st++;
|
|
@@ -3662,10 +4103,12 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
|
|
|
3662
4103
|
let maxSize = 0;
|
|
3663
4104
|
let mathAscent = 0;
|
|
3664
4105
|
let mathDescent = 0;
|
|
3665
|
-
const useMetric =
|
|
3666
|
-
let
|
|
3667
|
-
let
|
|
3668
|
-
let
|
|
4106
|
+
const useMetric = leading.model !== "flat";
|
|
4107
|
+
let textAscent = 0;
|
|
4108
|
+
let textDescent = 0;
|
|
4109
|
+
let markerAscent = 0;
|
|
4110
|
+
let markerDescent = 0;
|
|
4111
|
+
let pictureHeight = 0;
|
|
3669
4112
|
const prints = lineTokens.some((t) => !t.isSpace);
|
|
3670
4113
|
for (const t of lineTokens) {
|
|
3671
4114
|
contentWidth += t.widthPt;
|
|
@@ -3676,13 +4119,22 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
|
|
|
3676
4119
|
mathAscent = Math.max(mathAscent, t.ascentPt);
|
|
3677
4120
|
mathDescent = Math.max(mathDescent, t.descentPt);
|
|
3678
4121
|
} else if (useMetric && t.kind === "text") {
|
|
3679
|
-
const m = fontLeadingPt(t.font.parsed, t.fontSizePt,
|
|
3680
|
-
|
|
3681
|
-
if (
|
|
3682
|
-
|
|
3683
|
-
|
|
4122
|
+
const m = fontLeadingPt(t.font.parsed, t.fontSizePt, leading.model, wordFamilyOf(t));
|
|
4123
|
+
const above = m.ascentPt + m.lineGapPt;
|
|
4124
|
+
if (t.listMarker === true) {
|
|
4125
|
+
markerAscent = Math.max(markerAscent, above);
|
|
4126
|
+
markerDescent = Math.max(markerDescent, m.descentPt);
|
|
4127
|
+
} else {
|
|
4128
|
+
textAscent = Math.max(textAscent, above);
|
|
4129
|
+
textDescent = Math.max(textDescent, m.descentPt);
|
|
4130
|
+
}
|
|
4131
|
+
} else if (useMetric && t.kind === "image") pictureHeight = Math.max(pictureHeight, t.heightPt);
|
|
3684
4132
|
}
|
|
3685
|
-
const
|
|
4133
|
+
const hasText = textAscent + textDescent > 0;
|
|
4134
|
+
const ascent = Math.max(textAscent, markerAscent);
|
|
4135
|
+
const descent = hasText ? textDescent : markerDescent;
|
|
4136
|
+
const hasMetric = useMetric && ascent + descent + pictureHeight > 0;
|
|
4137
|
+
const textLine = ascent + descent > 0 ? ascent + descent : leading.markLinePt;
|
|
3686
4138
|
return {
|
|
3687
4139
|
tokens: lineTokens,
|
|
3688
4140
|
contentWidthPt: contentWidth,
|
|
@@ -3694,8 +4146,9 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
|
|
|
3694
4146
|
mathAscentPt: mathAscent,
|
|
3695
4147
|
mathDescentPt: mathDescent,
|
|
3696
4148
|
...hasMetric ? {
|
|
3697
|
-
metricHeightPt:
|
|
3698
|
-
metricDescentPt:
|
|
4149
|
+
metricHeightPt: Math.max(ascent, pictureHeight) + descent,
|
|
4150
|
+
metricDescentPt: descent,
|
|
4151
|
+
metricTextHeightPt: textLine
|
|
3699
4152
|
} : {}
|
|
3700
4153
|
};
|
|
3701
4154
|
}
|
|
@@ -3715,7 +4168,7 @@ function withLeadingTabWidths(tokens, resolved, firstLineWidth) {
|
|
|
3715
4168
|
}
|
|
3716
4169
|
return out;
|
|
3717
4170
|
}
|
|
3718
|
-
function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile, noExpandShiftReturn, lineWidths) {
|
|
4171
|
+
function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile, leading, noExpandShiftReturn, lineWidths) {
|
|
3719
4172
|
if (tokens.length === 0) return [];
|
|
3720
4173
|
const widths = lineWidths ?? [firstLineWidth, otherWidth];
|
|
3721
4174
|
const entries = paragraphItemStream(withLeadingTabWidths(tokens, resolved, widths[0] ?? firstLineWidth), hyphenator, Math.max(0, ...widths));
|
|
@@ -3728,7 +4181,7 @@ function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile,
|
|
|
3728
4181
|
let lineIdx = 0;
|
|
3729
4182
|
for (const [i, breakIdx] of at.entries()) {
|
|
3730
4183
|
const width = lineIdx < widths.length ? widths[lineIdx] : widths[widths.length - 1];
|
|
3731
|
-
const line = lineFromRange(entries, start, breakIdx, width, isFirst, resolved,
|
|
4184
|
+
const line = lineFromRange(entries, start, breakIdx, width, isFirst, resolved, leading, i < at.length - 1);
|
|
3732
4185
|
if (line) out.push(noExpandShiftReturn && entries[breakIdx]?.softBreak ? {
|
|
3733
4186
|
...line,
|
|
3734
4187
|
noJustify: true
|
|
@@ -3748,14 +4201,55 @@ function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile,
|
|
|
3748
4201
|
if (lines.length > 0) lines[lines.length - 1].isLastInParagraph = true;
|
|
3749
4202
|
return lines;
|
|
3750
4203
|
}
|
|
3751
|
-
|
|
4204
|
+
/**
|
|
4205
|
+
* §17.3.2.26 — the family Word sets a token's characters in: East Asian text
|
|
4206
|
+
* in the run's `eastAsia` font, complex-script text (and a `w:rtl` run) in its
|
|
4207
|
+
* `cs` one, the rest in `ascii` — `hAnsi` above ASCII. The layout draws a run
|
|
4208
|
+
* with its ascii face and hands what that face lacks to another, so the name
|
|
4209
|
+
* the run gives for THESE characters is the one Word's line is looked up by:
|
|
4210
|
+
* a Chinese sentence in a Calibri run does not stand on Calibri's line.
|
|
4211
|
+
*
|
|
4212
|
+
* @param t The text token.
|
|
4213
|
+
* @returns The family's name, when the run gives one for that slot.
|
|
4214
|
+
*/
|
|
4215
|
+
function wordFamilyOf(t) {
|
|
4216
|
+
const fonts = t.resolvedRun.fontFamily;
|
|
4217
|
+
if (t.resolvedRun.rtl) return fonts.cs;
|
|
4218
|
+
let high = false;
|
|
4219
|
+
for (const ch of t.text) {
|
|
4220
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
4221
|
+
if (cp <= 127) continue;
|
|
4222
|
+
high = true;
|
|
4223
|
+
const script = scriptForCodepoint(cp);
|
|
4224
|
+
if (script === "hebrew" || script === "arabic" || script === "thai") return fonts.cs;
|
|
4225
|
+
if (script === "kr" || script === "jp" || script === "sc") return fonts.eastAsia;
|
|
4226
|
+
}
|
|
4227
|
+
return high ? fonts.hAnsi ?? fonts.ascii : fonts.ascii;
|
|
4228
|
+
}
|
|
4229
|
+
/**
|
|
4230
|
+
* A face's single line at a size: its ascent, its descent, and the gap set
|
|
4231
|
+
* above the text. Under `'word'` a family Word's own line is known for (see
|
|
4232
|
+
* {@link WORD_LINE_METRICS}) takes that; otherwise the face in hand gives its
|
|
4233
|
+
* hhea line, or the OS/2 typo one when it sets USE_TYPO_METRICS.
|
|
4234
|
+
*
|
|
4235
|
+
* @param parsed The face the run is drawn with.
|
|
4236
|
+
* @param sizePt The run's size.
|
|
4237
|
+
* @param model The line model (never `'flat'`, which asks no face).
|
|
4238
|
+
* @param family The family the run names, when it names one.
|
|
4239
|
+
* @returns The three, in points; the descent as a magnitude.
|
|
4240
|
+
*/
|
|
4241
|
+
function fontLeadingPt(parsed, sizePt, model, family) {
|
|
4242
|
+
const known = model === "word" && family !== void 0 ? WORD_LINE_METRICS.get(family.trim().toLowerCase()) : void 0;
|
|
4243
|
+
if (known) {
|
|
4244
|
+
const k = sizePt / 2048;
|
|
4245
|
+
return {
|
|
4246
|
+
ascentPt: known[0] * k,
|
|
4247
|
+
lineGapPt: known[1] * k,
|
|
4248
|
+
descentPt: known[2] * k
|
|
4249
|
+
};
|
|
4250
|
+
}
|
|
3752
4251
|
const s = sizePt / parsed.unitsPerEm;
|
|
3753
4252
|
const vm = parsed.vmetrics;
|
|
3754
|
-
if (profile === "word") return {
|
|
3755
|
-
ascentPt: vm.winAscent * s,
|
|
3756
|
-
descentPt: vm.winDescent * s,
|
|
3757
|
-
lineGapPt: 0
|
|
3758
|
-
};
|
|
3759
4253
|
const asc = vm.useTypoMetrics ? vm.typoAscent : parsed.ascender;
|
|
3760
4254
|
const desc = vm.useTypoMetrics ? vm.typoDescent : parsed.descender;
|
|
3761
4255
|
const gap = vm.useTypoMetrics ? vm.typoLineGap : parsed.lineGap;
|
|
@@ -3780,7 +4274,8 @@ function naturalLineHeight(line, p) {
|
|
|
3780
4274
|
if (p.spacingLineRule === "atLeast" && p.spacingLine > 0) return Math.max(natural, p.spacingLine, mathNeed);
|
|
3781
4275
|
const lineTwips = Math.round(p.spacingLine * 20);
|
|
3782
4276
|
const multiple = lineTwips > 0 ? lineTwips / 240 : 1;
|
|
3783
|
-
|
|
4277
|
+
const scaled = line.metricTextHeightPt === void 0 ? natural * multiple : natural + (multiple - 1) * line.metricTextHeightPt;
|
|
4278
|
+
return Math.max(scaled, mathNeed);
|
|
3784
4279
|
}
|
|
3785
4280
|
function lineDescent(line) {
|
|
3786
4281
|
const fs = line.maxFontSizePt || 12;
|
|
@@ -3931,7 +4426,7 @@ function layoutTableBlock(table, options, fontResources, imageResources, content
|
|
|
3931
4426
|
growRowsForMerges(rows);
|
|
3932
4427
|
const heightPt = rows.reduce((s, r) => s + r.heightPt, 0);
|
|
3933
4428
|
const totalWidthPt = columnWidthsPt.reduce((s, w) => s + w, 0);
|
|
3934
|
-
const xOffsetPt = (table.properties.indentPt ?? 0) + tableXOffset(table.properties.alignment, contentWidth, totalWidthPt);
|
|
4429
|
+
const xOffsetPt = (table.properties.indentPt ?? 0) + tableXOffset(table.properties.alignment, contentWidth, totalWidthPt) - legacyTableOutdent(table, rows, options);
|
|
3935
4430
|
return {
|
|
3936
4431
|
kind: "table",
|
|
3937
4432
|
...table.properties.float ? { float: table.properties.float } : {},
|
|
@@ -3968,6 +4463,29 @@ function growRowsForMerges(rows) {
|
|
|
3968
4463
|
};
|
|
3969
4464
|
}
|
|
3970
4465
|
}
|
|
4466
|
+
/**
|
|
4467
|
+
* [MS-DOCX] `compatibilityMode` — how far a table stands out past its indent
|
|
4468
|
+
* in a document laid out as Word 2010 and earlier lay it out. There the
|
|
4469
|
+
* indent places the first cell's TEXT, not the table's edge, so the table
|
|
4470
|
+
* stands out by that cell's left margin. Measured in Word for Mac: with
|
|
4471
|
+
* first-cell margins of 5.4, 15 and 20pt, the text stands at the margin in a
|
|
4472
|
+
* document of mode 14 and in one that states no mode, and 5.4, 15 and 20pt in
|
|
4473
|
+
* at mode 15. A 36pt `w:tblInd` puts the text 36pt in, not 41.4. A centred
|
|
4474
|
+
* table does not move. Only a Word document's table moves; a spreadsheet has
|
|
4475
|
+
* no such history.
|
|
4476
|
+
*
|
|
4477
|
+
* @param table The table.
|
|
4478
|
+
* @param rows Its laid-out rows, the first cell's margin resolved.
|
|
4479
|
+
* @param options Render options: whose document, and which mode.
|
|
4480
|
+
* @returns The points to move the table left by, 0 where it stays.
|
|
4481
|
+
*/
|
|
4482
|
+
function legacyTableOutdent(table, rows, options) {
|
|
4483
|
+
if (options.typesetBy !== "word" || (options.compatibilityMode ?? 0) >= 15) return 0;
|
|
4484
|
+
if ((table.properties.alignment ?? "left") !== "left" || table.properties.float) return 0;
|
|
4485
|
+
const first = rows[0];
|
|
4486
|
+
if (!first || first.columnXOffsets[0] !== 0) return 0;
|
|
4487
|
+
return first.cells[0]?.padLeftPt ?? 0;
|
|
4488
|
+
}
|
|
3971
4489
|
function tableXOffset(alignment, contentWidth, totalWidthPt) {
|
|
3972
4490
|
const slack = contentWidth - totalWidthPt;
|
|
3973
4491
|
if (slack <= 0 || !alignment || alignment === "left") return 0;
|
|
@@ -4126,7 +4644,7 @@ function measureSingleLine(paragraph, options, fontResources) {
|
|
|
4126
4644
|
const { fontKey, synthetic } = runFontKeyAndParsed(options, resolved.fontFamily.ascii, resolved.bold, resolved.italic);
|
|
4127
4645
|
const font = lookupFont(fontResources, fontKey);
|
|
4128
4646
|
const fontSizePt = resolved.fontSizePt;
|
|
4129
|
-
total += font.measure.textWidthPt(run.text, fontSizePt) * (synthetic?.widthScale ?? 1);
|
|
4647
|
+
total += font.measure.textWidthPt(run.text, fontSizePt) * (synthetic?.widthScale ?? 1) * (resolved.widthScale ?? 1);
|
|
4130
4648
|
}
|
|
4131
4649
|
return total;
|
|
4132
4650
|
}
|
|
@@ -4179,6 +4697,13 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
|
|
|
4179
4697
|
cursorX += widthPt;
|
|
4180
4698
|
colIdx += span;
|
|
4181
4699
|
}
|
|
4700
|
+
const bands = options.typesetBy === "word" ? withBorderBands(cells, rowIdx === rowCount - 1) : void 0;
|
|
4701
|
+
const bandSpread = bands && (bands.topPt > 0 || bands.bottomPt > 0) ? bands : void 0;
|
|
4702
|
+
if (bandSpread) cells.splice(0, cells.length, ...bandSpread.cells);
|
|
4703
|
+
const borderBands = bandSpread ? { borderBands: {
|
|
4704
|
+
topPt: bandSpread.topPt,
|
|
4705
|
+
bottomPt: bandSpread.bottomPt
|
|
4706
|
+
} } : {};
|
|
4182
4707
|
let heightPt = 0;
|
|
4183
4708
|
for (const c of cells) {
|
|
4184
4709
|
if (c.mergeRole === "start") continue;
|
|
@@ -4192,7 +4717,8 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
|
|
|
4192
4717
|
columnXOffsets,
|
|
4193
4718
|
rowIdx,
|
|
4194
4719
|
rowCount,
|
|
4195
|
-
...row.properties.cantSplit ? { cantSplit: true } : {}
|
|
4720
|
+
...row.properties.cantSplit ? { cantSplit: true } : {},
|
|
4721
|
+
...borderBands
|
|
4196
4722
|
};
|
|
4197
4723
|
} else if (row.properties.height && row.properties.heightRule === "atLeast") heightPt = Math.max(heightPt, row.properties.height);
|
|
4198
4724
|
return {
|
|
@@ -4201,7 +4727,42 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
|
|
|
4201
4727
|
columnXOffsets,
|
|
4202
4728
|
rowIdx,
|
|
4203
4729
|
rowCount,
|
|
4204
|
-
...row.properties.cantSplit ? { cantSplit: true } : {}
|
|
4730
|
+
...row.properties.cantSplit ? { cantSplit: true } : {},
|
|
4731
|
+
...borderBands
|
|
4732
|
+
};
|
|
4733
|
+
}
|
|
4734
|
+
/**
|
|
4735
|
+
* §17.4.38 — a table row as Word sets it: every horizontal border takes the
|
|
4736
|
+
* room it is wide, the edge above a row in that row and the table's bottom
|
|
4737
|
+
* edge in its last one, and the text stands clear of it. Measured in Word for
|
|
4738
|
+
* Mac, a row of 11pt Calibri with no cell margins stands 13.44pt apart from
|
|
4739
|
+
* the next with no borders, 13.92 under 0.5pt ones, 14.94 under 1.5pt and
|
|
4740
|
+
* 16.43 under 3pt; a one-row table between two paragraphs adds its top and
|
|
4741
|
+
* its bottom border whole, 3pt each at 3pt. LibreOffice does the same. The
|
|
4742
|
+
* room is the widest border along the edge, and it goes into the cells' top
|
|
4743
|
+
* and bottom padding, which is what places their text and sizes the row.
|
|
4744
|
+
*
|
|
4745
|
+
* @param cells The row's laid-out cells, their borders resolved.
|
|
4746
|
+
* @param lastRow Whether the row is the table's last, whose bottom border is
|
|
4747
|
+
* the table's own.
|
|
4748
|
+
* @returns The cells with the room in their padding, and the room taken.
|
|
4749
|
+
*/
|
|
4750
|
+
function withBorderBands(cells, lastRow) {
|
|
4751
|
+
let topPt = 0;
|
|
4752
|
+
let bottomPt = 0;
|
|
4753
|
+
for (const c of cells) {
|
|
4754
|
+
topPt = Math.max(topPt, borderWeight(c.borders.top));
|
|
4755
|
+
if (lastRow) bottomPt = Math.max(bottomPt, borderWeight(c.borders.bottom));
|
|
4756
|
+
}
|
|
4757
|
+
return {
|
|
4758
|
+
cells: cells.map((c) => ({
|
|
4759
|
+
...c,
|
|
4760
|
+
padTopPt: c.padTopPt + topPt,
|
|
4761
|
+
padBottomPt: c.padBottomPt + bottomPt,
|
|
4762
|
+
totalHeightPt: c.totalHeightPt + topPt + bottomPt
|
|
4763
|
+
})),
|
|
4764
|
+
topPt,
|
|
4765
|
+
bottomPt
|
|
4205
4766
|
};
|
|
4206
4767
|
}
|
|
4207
4768
|
/**
|
|
@@ -4278,8 +4839,9 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
4278
4839
|
let contentHeightPt = 0;
|
|
4279
4840
|
let clipped = false;
|
|
4280
4841
|
let pendingGapPt = 0;
|
|
4842
|
+
const collapse = collapsesSpacing(options);
|
|
4281
4843
|
const openParagraph = (spacingBeforePt) => {
|
|
4282
|
-
const gap = pendingGapPt + spacingBeforePt;
|
|
4844
|
+
const gap = collapse ? Math.max(pendingGapPt, spacingBeforePt) : pendingGapPt + spacingBeforePt;
|
|
4283
4845
|
pendingGapPt = 0;
|
|
4284
4846
|
if (gap <= 0) return;
|
|
4285
4847
|
lineGaps.set(lines.length, (lineGaps.get(lines.length) ?? 0) + gap);
|
|
@@ -4316,7 +4878,6 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
4316
4878
|
lines.push(line);
|
|
4317
4879
|
contentHeightPt += computeLineHeight(line, block.resolved);
|
|
4318
4880
|
}
|
|
4319
|
-
contentHeightPt += block.spacingAfterPt;
|
|
4320
4881
|
pendingGapPt = block.spacingAfterPt;
|
|
4321
4882
|
} else if (el.kind === "shape" && !isOutOfFlowFloat(el.shape.float)) {
|
|
4322
4883
|
const block = layoutShapeBlock(el.shape, options, fontResources, imageResources, innerWidth);
|
|
@@ -4492,7 +5053,7 @@ var PageAssembler = class PageAssembler {
|
|
|
4492
5053
|
this.ctx = sectionCtxs[0];
|
|
4493
5054
|
this.pageStartCtx = this.ctx;
|
|
4494
5055
|
this.pageNumber = this.ctx.properties.pageNumberStart ?? 1;
|
|
4495
|
-
this.cursorY = this.
|
|
5056
|
+
this.cursorY = this.pageTopY();
|
|
4496
5057
|
this.colStartY = this.cursorY;
|
|
4497
5058
|
this.bandTopY = this.cursorY;
|
|
4498
5059
|
}
|
|
@@ -4509,6 +5070,56 @@ var PageAssembler = class PageAssembler {
|
|
|
4509
5070
|
current = [];
|
|
4510
5071
|
pendingPageBreak = false;
|
|
4511
5072
|
cursorY;
|
|
5073
|
+
/** Whether paragraph spacing stacks as Word stacks it (see {@link SpacingStack}). */
|
|
5074
|
+
collapseSpacing = false;
|
|
5075
|
+
/**
|
|
5076
|
+
* Where the last block's space after ended, on which page, and how much of
|
|
5077
|
+
* it a paragraph right below may share — nothing once the cursor has moved
|
|
5078
|
+
* on, to a new column or page or past a table.
|
|
5079
|
+
*/
|
|
5080
|
+
spacingMark = void 0;
|
|
5081
|
+
/**
|
|
5082
|
+
* Whether the next block opens a section on a page of its own (the
|
|
5083
|
+
* document's first included), whose space before Word keeps at the top.
|
|
5084
|
+
*/
|
|
5085
|
+
sectionStart = true;
|
|
5086
|
+
/**
|
|
5087
|
+
* Whether a paragraph that breaks to a new page by its own
|
|
5088
|
+
* `w:pageBreakBefore` keeps its space before there — Word 2010 and earlier
|
|
5089
|
+
* (compatibilityMode below 15, or none) keep it; Word 2013 drops it.
|
|
5090
|
+
*/
|
|
5091
|
+
keepsBeforeAtOwnBreak = false;
|
|
5092
|
+
/**
|
|
5093
|
+
* §17.3.1.33 — the room to open above a block, where the spacing stacks as
|
|
5094
|
+
* Word stacks it (see {@link SpacingStack}): the space after the block right
|
|
5095
|
+
* above it shared, and nothing at all at the top of a page or column. Word
|
|
5096
|
+
* for Mac drops a 24pt space before there whether the paragraph ran over
|
|
5097
|
+
* onto the page, broke to it after a `w:br w:type="page"` or ran into the
|
|
5098
|
+
* next column, in every mode; it keeps it for the first paragraph of the
|
|
5099
|
+
* document and of a section, and — below mode 15 — for a paragraph that
|
|
5100
|
+
* breaks to the page by its own `w:pageBreakBefore`.
|
|
5101
|
+
*
|
|
5102
|
+
* @param beforePt A block's own space before.
|
|
5103
|
+
* @param opensSection Whether the block is the first of a section on a page of its own.
|
|
5104
|
+
* @param ownBreak Whether the block broke to this page by its own `w:pageBreakBefore`.
|
|
5105
|
+
* @returns The room to open above it.
|
|
5106
|
+
*/
|
|
5107
|
+
openBefore(beforePt, opensSection = false, ownBreak = false) {
|
|
5108
|
+
const mark = this.spacingMark;
|
|
5109
|
+
this.spacingMark = void 0;
|
|
5110
|
+
if (!this.collapseSpacing) return beforePt;
|
|
5111
|
+
if (!this.colHasContent()) return opensSection || ownBreak && this.keepsBeforeAtOwnBreak ? beforePt : 0;
|
|
5112
|
+
if (!mark || mark.page !== this.current || mark.y !== this.cursorY) return beforePt;
|
|
5113
|
+
return Math.max(0, beforePt - mark.shared);
|
|
5114
|
+
}
|
|
5115
|
+
/** @param sharedPt The space after the block just placed, as far as a paragraph below may share it. */
|
|
5116
|
+
closeAfter(sharedPt) {
|
|
5117
|
+
this.spacingMark = {
|
|
5118
|
+
y: this.cursorY,
|
|
5119
|
+
page: this.current,
|
|
5120
|
+
shared: sharedPt
|
|
5121
|
+
};
|
|
5122
|
+
}
|
|
4512
5123
|
/**
|
|
4513
5124
|
* §17.6.4 multi-column flow: content fills column after column before the page
|
|
4514
5125
|
* flushes. `colStartLen` marks where the current column's items begin in
|
|
@@ -4557,6 +5168,16 @@ var PageAssembler = class PageAssembler {
|
|
|
4557
5168
|
* all and eleven controls wider than its page.
|
|
4558
5169
|
*/
|
|
4559
5170
|
pageHasContent = () => this.current.length > 0 || this.floatsBehind.length > 0 || this.floatsFront.length > 0;
|
|
5171
|
+
/**
|
|
5172
|
+
* §17.10 — the body's margins on the page being laid out: below the header
|
|
5173
|
+
* band that page shows and above its footer band (see `bandMargins`).
|
|
5174
|
+
*/
|
|
5175
|
+
pageMargins = () => this.ctx.bandMargins?.[bandForPage(this.pageInSection, this.globalPageIdx, this.ctx.titlePg, this.ctx.evenAndOddHeaders)] ?? {
|
|
5176
|
+
top: this.ctx.marginTop,
|
|
5177
|
+
bottom: this.ctx.marginBottom
|
|
5178
|
+
};
|
|
5179
|
+
/** Where the body of the page being laid out begins, y-up. */
|
|
5180
|
+
pageTopY = () => this.ctx.pageHeight - this.pageMargins().top;
|
|
4560
5181
|
/** Overflow step: next column on this page, or a fresh page after the last. */
|
|
4561
5182
|
advanceColumn = () => {
|
|
4562
5183
|
if (this.ctx.columns && this.colIdx + 1 < this.ctx.columns.length) {
|
|
@@ -4652,7 +5273,7 @@ var PageAssembler = class PageAssembler {
|
|
|
4652
5273
|
*/
|
|
4653
5274
|
beginBalancedBand = (bandHeightPt) => {
|
|
4654
5275
|
const colCount = this.ctx.columns?.length ?? 1;
|
|
4655
|
-
const room = this.cursorY - (this.
|
|
5276
|
+
const room = this.cursorY - (this.pageMargins().bottom + this.noteReserve);
|
|
4656
5277
|
const share = colCount > 1 ? balancedColumnHeight(bandHeightPt, colCount, room) : 0;
|
|
4657
5278
|
this.balanceHeightPt = share;
|
|
4658
5279
|
this.balanceBottomY = share > 0 ? this.cursorY - share : void 0;
|
|
@@ -4737,7 +5358,7 @@ var PageAssembler = class PageAssembler {
|
|
|
4737
5358
|
* margin (plus any footnote reserve); inside a BALANCED band it is the line
|
|
4738
5359
|
* the band's share of the height reaches, so the columns come out even.
|
|
4739
5360
|
*/
|
|
4740
|
-
bottomLimit = () => Math.max(this.
|
|
5361
|
+
bottomLimit = () => Math.max(this.pageMargins().bottom + this.noteReserve, this.balanceBottomY ?? Number.NEGATIVE_INFINITY);
|
|
4741
5362
|
/**
|
|
4742
5363
|
* §17.6.4 — a `continuous` section of several columns whose content fits the
|
|
4743
5364
|
* page is BALANCED: Word evens the columns out rather than filling the first
|
|
@@ -4772,8 +5393,8 @@ var PageAssembler = class PageAssembler {
|
|
|
4772
5393
|
xOffset: 0
|
|
4773
5394
|
};
|
|
4774
5395
|
const cl = this.colLeft();
|
|
4775
|
-
const leftRoom = e.x0 -
|
|
4776
|
-
const rightRoom = cl + full -
|
|
5396
|
+
const leftRoom = e.x0 - cl;
|
|
5397
|
+
const rightRoom = cl + full - e.x1;
|
|
4777
5398
|
if (Math.max(leftRoom, rightRoom) < MIN_WRAP_WIDTH) return {
|
|
4778
5399
|
width: full,
|
|
4779
5400
|
xOffset: 0,
|
|
@@ -4806,19 +5427,22 @@ var PageAssembler = class PageAssembler {
|
|
|
4806
5427
|
if (this.exclusions.length === 0 || block.lines.length === 0) return void 0;
|
|
4807
5428
|
const h0 = computeLineHeight(block.lines[0], block.resolved);
|
|
4808
5429
|
if (h0 <= 0) return void 0;
|
|
5430
|
+
const p = block.resolved;
|
|
5431
|
+
const full = this.colWidth();
|
|
5432
|
+
const hold = (width, xOffset, first) => Math.max(1, (xOffset > 0 ? width - p.indentRight : Math.min(width, full - p.indentRight)) - p.indentLeft - (first ? p.indentFirstLine : 0));
|
|
4809
5433
|
const widths = [];
|
|
4810
5434
|
let narrowed = false;
|
|
4811
5435
|
let y = startY;
|
|
4812
5436
|
for (let i = 0; i < 200; i++) {
|
|
4813
5437
|
const g = this.lineGeometryAt(y, h0);
|
|
4814
5438
|
if (g.blockedUntilYUp !== void 0) {
|
|
4815
|
-
widths.push(
|
|
5439
|
+
widths.push(hold(full, 0, widths.length === 0));
|
|
4816
5440
|
y = g.blockedUntilYUp - h0;
|
|
4817
5441
|
if (y < this.bottomLimit()) break;
|
|
4818
5442
|
continue;
|
|
4819
5443
|
}
|
|
4820
|
-
widths.push(g.width);
|
|
4821
|
-
if (g.pair) widths.push(g.pair.width);
|
|
5444
|
+
widths.push(hold(g.width, g.xOffset, widths.length === 0));
|
|
5445
|
+
if (g.pair) widths.push(hold(g.pair.width, g.pair.xOffset, false));
|
|
4822
5446
|
if (g.width < this.colWidth()) narrowed = true;
|
|
4823
5447
|
else if (narrowed) break;
|
|
4824
5448
|
y -= h0;
|
|
@@ -4850,13 +5474,43 @@ var PageAssembler = class PageAssembler {
|
|
|
4850
5474
|
return out;
|
|
4851
5475
|
};
|
|
4852
5476
|
/**
|
|
5477
|
+
* The first of a paragraph's lines, from `from` on, that the current column
|
|
5478
|
+
* no longer holds: each line is counted with the notes it brings onto the
|
|
5479
|
+
* page, the way the line loop places it, and an explicit column break ends
|
|
5480
|
+
* the column where it stands.
|
|
5481
|
+
*
|
|
5482
|
+
* @param pb The paragraph being placed.
|
|
5483
|
+
* @param from The first line not yet placed.
|
|
5484
|
+
* @returns The index of the first line that does not stay in this column;
|
|
5485
|
+
* `pb.lines.length` when every one does.
|
|
5486
|
+
*/
|
|
5487
|
+
firstLineOverflowing = (pb, from) => {
|
|
5488
|
+
let y = this.cursorY;
|
|
5489
|
+
let reserve = 0;
|
|
5490
|
+
let notesOnPage = this.pageNotes.length > 0;
|
|
5491
|
+
const counted = /* @__PURE__ */ new Set();
|
|
5492
|
+
for (let i = from; i < pb.lines.length; i++) {
|
|
5493
|
+
if (i > from && pb.columnBreakLines?.has(i)) return i;
|
|
5494
|
+
const line = pb.lines[i];
|
|
5495
|
+
const h = computeLineHeight(line, pb.resolved);
|
|
5496
|
+
const notes = this.lineFootnotes(line).filter((x) => !counted.has(x.id));
|
|
5497
|
+
const added = notes.reduce((sum, x) => sum + x.heightPt, 0) + (!notesOnPage && notes.length > 0 ? FOOTNOTE_SEPARATOR_HEIGHT : 0);
|
|
5498
|
+
if (y - h < this.bottomLimit() + reserve + added) return i;
|
|
5499
|
+
y -= h;
|
|
5500
|
+
reserve += added;
|
|
5501
|
+
if (notes.length > 0) notesOnPage = true;
|
|
5502
|
+
for (const x of notes) counted.add(x.id);
|
|
5503
|
+
}
|
|
5504
|
+
return pb.lines.length;
|
|
5505
|
+
};
|
|
5506
|
+
/**
|
|
4853
5507
|
* The notes band for the flushing page: separator rule + each note's blocks
|
|
4854
5508
|
* stacked inside the reserved area. Tagged: each note is a Note→P element.
|
|
4855
5509
|
*/
|
|
4856
5510
|
renderNotesBand = () => {
|
|
4857
5511
|
if (this.pageNotes.length === 0) return [];
|
|
4858
5512
|
const out = [];
|
|
4859
|
-
const top = this.
|
|
5513
|
+
const top = this.pageMargins().bottom + this.noteReserve;
|
|
4860
5514
|
out.push({
|
|
4861
5515
|
type: "fill",
|
|
4862
5516
|
x: pt(this.ctx.marginLeft),
|
|
@@ -4872,7 +5526,7 @@ var PageAssembler = class PageAssembler {
|
|
|
4872
5526
|
const noteNode = this.builder.create("Note", this.builder.root);
|
|
4873
5527
|
structId = this.builder.create("P", noteNode).id;
|
|
4874
5528
|
}
|
|
4875
|
-
out.push(...drawBlocksSequentially(note.blocks, this.ctx.marginLeft, cursor, this.ctx.pageHeight, this.ctx.contentWidth, structId));
|
|
5529
|
+
out.push(...drawBlocksSequentially(note.blocks, this.ctx.marginLeft, cursor, this.ctx.pageHeight, this.ctx.contentWidth, structId, void 0, this.collapseSpacing));
|
|
4876
5530
|
cursor -= note.heightPt;
|
|
4877
5531
|
}
|
|
4878
5532
|
return out;
|
|
@@ -4895,35 +5549,38 @@ var PageAssembler = class PageAssembler {
|
|
|
4895
5549
|
const band = bandForPage(this.pageInSection, this.globalPageIdx, this.ctx.titlePg, this.ctx.evenAndOddHeaders);
|
|
4896
5550
|
const header = pickBand(this.ctx.headerSet, band);
|
|
4897
5551
|
const footer = pickBand(this.ctx.footerSet, band);
|
|
5552
|
+
const pageText = pageNumberText(this.pageNumber, this.ctx.properties.pageNumberFormat);
|
|
4898
5553
|
if (header.renderDynamic) this.dynBands.push({
|
|
4899
5554
|
pageIdx: this.pages.length,
|
|
4900
|
-
|
|
5555
|
+
pageText,
|
|
4901
5556
|
position: "header",
|
|
4902
5557
|
render: header.renderDynamic
|
|
4903
5558
|
});
|
|
4904
5559
|
if (footer.renderDynamic) this.dynBands.push({
|
|
4905
5560
|
pageIdx: this.pages.length,
|
|
4906
|
-
|
|
5561
|
+
pageText,
|
|
4907
5562
|
position: "footer",
|
|
4908
5563
|
render: footer.renderDynamic
|
|
4909
5564
|
});
|
|
5565
|
+
const sheet = this.ctx.sheet;
|
|
5566
|
+
const onSheet = (items) => sheet ? items.map((item) => turnOntoSheet(item, sheet.width)) : items;
|
|
4910
5567
|
this.pages.push({
|
|
4911
5568
|
commands: [
|
|
4912
|
-
...this.pageBackgroundItems(),
|
|
4913
|
-
...this.pageBorderItems(),
|
|
4914
|
-
...this.columnSeparatorItems(),
|
|
5569
|
+
...onSheet(this.pageBackgroundItems()),
|
|
5570
|
+
...onSheet(this.pageBorderItems()),
|
|
5571
|
+
...onSheet(this.columnSeparatorItems()),
|
|
4915
5572
|
...header.commands,
|
|
4916
|
-
...PageAssembler.byZ(this.floatsBehind).map((item) => ({
|
|
5573
|
+
...onSheet(PageAssembler.byZ(this.floatsBehind).map((item) => ({
|
|
4917
5574
|
...item,
|
|
4918
5575
|
behind: true
|
|
4919
|
-
})),
|
|
4920
|
-
...this.current,
|
|
4921
|
-
...PageAssembler.byZ(this.floatsFront),
|
|
4922
|
-
...this.renderNotesBand(),
|
|
5576
|
+
}))),
|
|
5577
|
+
...onSheet(this.current),
|
|
5578
|
+
...onSheet(PageAssembler.byZ(this.floatsFront)),
|
|
5579
|
+
...onSheet(this.renderNotesBand()),
|
|
4923
5580
|
...footer.commands
|
|
4924
5581
|
],
|
|
4925
|
-
width: pt(this.ctx.pageWidth),
|
|
4926
|
-
height: pt(this.ctx.pageHeight)
|
|
5582
|
+
width: pt(sheet?.width ?? this.ctx.pageWidth),
|
|
5583
|
+
height: pt(sheet?.height ?? this.ctx.pageHeight)
|
|
4927
5584
|
});
|
|
4928
5585
|
this.current = [];
|
|
4929
5586
|
this.floatsBehind = [];
|
|
@@ -4937,7 +5594,7 @@ var PageAssembler = class PageAssembler {
|
|
|
4937
5594
|
this.globalPageIdx++;
|
|
4938
5595
|
this.pageNumber++;
|
|
4939
5596
|
if ((this.ctx.properties.lineNumbering?.restart ?? "newPage") === "newPage") this.lineNumber = 0;
|
|
4940
|
-
this.cursorY = this.
|
|
5597
|
+
this.cursorY = this.pageTopY();
|
|
4941
5598
|
this.colStartY = this.cursorY;
|
|
4942
5599
|
this.bandTopY = this.cursorY;
|
|
4943
5600
|
this.balanceBottomY = void 0;
|
|
@@ -5126,32 +5783,80 @@ var PageAssembler = class PageAssembler {
|
|
|
5126
5783
|
this.noteReserve = 0;
|
|
5127
5784
|
this.colIdx = 0;
|
|
5128
5785
|
this.colStartLen = 0;
|
|
5129
|
-
this.cursorY = this.
|
|
5786
|
+
this.cursorY = this.pageTopY();
|
|
5130
5787
|
this.colStartY = this.cursorY;
|
|
5131
5788
|
this.bandTopY = this.cursorY;
|
|
5132
5789
|
this.balanceBottomY = void 0;
|
|
5133
5790
|
this.balanceHeightPt = 0;
|
|
5134
5791
|
};
|
|
5135
5792
|
};
|
|
5136
|
-
|
|
5793
|
+
/**
|
|
5794
|
+
* §17.3.1.14/15 — the height a paragraph needs in one column: all of its lines
|
|
5795
|
+
* where it keeps them together, and where it keeps with the next, the whole of
|
|
5796
|
+
* every paragraph in the chain it heads and as much of the block after as has
|
|
5797
|
+
* to stand beside it — a paragraph's first line, or its first two (all of a
|
|
5798
|
+
* short one) where its own widow control would not leave one alone; a table's
|
|
5799
|
+
* first row; a picture. The space between them counts, the chain's own space
|
|
5800
|
+
* before does not. A chain is decided at its head: one that follows a
|
|
5801
|
+
* paragraph kept with it moved with that paragraph or not at all, and asks
|
|
5802
|
+
* only whether its own lines stay together.
|
|
5803
|
+
*
|
|
5804
|
+
* @param blocks The section's blocks.
|
|
5805
|
+
* @param from The paragraph asked about.
|
|
5806
|
+
* @param end The section's end: nothing is kept with what lies past it.
|
|
5807
|
+
* @returns The height, or undefined where the paragraph asks for neither.
|
|
5808
|
+
*/
|
|
5809
|
+
function keptTogetherHeight(blocks, from, end, collapse = false) {
|
|
5810
|
+
const first = blocks[from];
|
|
5811
|
+
if (first?.kind !== "paragraph") return void 0;
|
|
5812
|
+
const before = blocks[from - 1];
|
|
5813
|
+
const inChain = before?.kind === "paragraph" && before.resolved.keepNext && !before.pageBreakAfter && !first.resolved.pageBreakBefore && !first.pageBreakBefore;
|
|
5814
|
+
const keepsNext = first.resolved.keepNext && !inChain;
|
|
5815
|
+
if (!keepsNext && !first.resolved.keepLines) return void 0;
|
|
5816
|
+
let height = first.heightPt;
|
|
5817
|
+
let current = first;
|
|
5818
|
+
for (let i = from + 1; keepsNext && current.resolved.keepNext && !current.pageBreakAfter; i++) {
|
|
5819
|
+
const next = i < end ? blocks[i] : void 0;
|
|
5820
|
+
if (next?.kind === "paragraph") {
|
|
5821
|
+
if (next.resolved.pageBreakBefore || next.pageBreakBefore) break;
|
|
5822
|
+
height += collapse ? Math.max(current.spacingAfterPt, next.spacingBeforePt) : current.spacingAfterPt + next.spacingBeforePt;
|
|
5823
|
+
if (next.resolved.keepNext) {
|
|
5824
|
+
height += next.heightPt;
|
|
5825
|
+
current = next;
|
|
5826
|
+
continue;
|
|
5827
|
+
}
|
|
5828
|
+
const count = next.lines.length;
|
|
5829
|
+
const stays = next.resolved.keepLines ? count : next.resolved.widowControl ? count <= 3 ? count : 2 : 1;
|
|
5830
|
+
for (const line of next.lines.slice(0, stays)) height += computeLineHeight(line, next.resolved);
|
|
5831
|
+
} else if (next?.kind === "table" && !next.float) height += current.spacingAfterPt + (next.rows[0]?.heightPt ?? 0);
|
|
5832
|
+
else if (next?.kind === "image" && !next.float) height += current.spacingAfterPt + next.heightPt;
|
|
5833
|
+
break;
|
|
5834
|
+
}
|
|
5835
|
+
return height;
|
|
5836
|
+
}
|
|
5837
|
+
function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph, backgroundImage, typesetByWord = false, compatibilityMode) {
|
|
5137
5838
|
if (sectionCtxs.length === 0) return [];
|
|
5138
5839
|
const asm = new PageAssembler(sectionCtxs, builder, notes, bookmarkPositions, backgroundImage);
|
|
5840
|
+
asm.collapseSpacing = typesetByWord;
|
|
5841
|
+
asm.keepsBeforeAtOwnBreak = (compatibilityMode ?? 0) < 15;
|
|
5139
5842
|
const balanceIfEndsContinuous = (fromBlock) => {
|
|
5140
5843
|
const here = asm.ctx;
|
|
5141
5844
|
const next = sectionCtxs[asm.secIdx + 1];
|
|
5142
5845
|
if ((here.columns?.length ?? 1) < 2) return;
|
|
5143
5846
|
if (!next?.continuous) return;
|
|
5144
5847
|
if (next.pageWidth !== here.pageWidth || next.pageHeight !== here.pageHeight) return;
|
|
5848
|
+
if (next.sheet === void 0 !== (here.sheet === void 0)) return;
|
|
5145
5849
|
const end = Math.min(here.endIndex, blocks.length);
|
|
5146
|
-
asm.beginBalancedBand(blocksHeight(blocks.slice(fromBlock, end)));
|
|
5850
|
+
asm.beginBalancedBand(blocksHeight(blocks.slice(fromBlock, end), typesetByWord));
|
|
5147
5851
|
};
|
|
5148
5852
|
balanceIfEndsContinuous(0);
|
|
5853
|
+
let brokenFor;
|
|
5149
5854
|
for (let blockIdx = 0; blockIdx < blocks.length; blockIdx++) {
|
|
5150
5855
|
while (asm.secIdx < sectionCtxs.length - 1 && blockIdx >= asm.ctx.endIndex) {
|
|
5151
5856
|
const next = sectionCtxs[asm.secIdx + 1];
|
|
5152
|
-
if (next.continuous && next.pageWidth === asm.ctx.pageWidth && next.pageHeight === asm.ctx.pageHeight) {
|
|
5857
|
+
if (next.continuous && next.pageWidth === asm.ctx.pageWidth && next.pageHeight === asm.ctx.pageHeight && next.sheet === void 0 === (asm.ctx.sheet === void 0)) {
|
|
5153
5858
|
const end = Math.min(next.endIndex, blocks.length);
|
|
5154
|
-
asm.startBandSection(next, blocksHeight(blocks.slice(blockIdx, end)));
|
|
5859
|
+
asm.startBandSection(next, blocksHeight(blocks.slice(blockIdx, end), typesetByWord));
|
|
5155
5860
|
continue;
|
|
5156
5861
|
}
|
|
5157
5862
|
if (asm.pageHasContent() || asm.pageInSection === 0) asm.flushPage(true);
|
|
@@ -5160,7 +5865,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
5160
5865
|
const wants = opening?.sectionStart;
|
|
5161
5866
|
if (wants === "oddPage" || wants === "evenPage") {
|
|
5162
5867
|
if ((opening?.pageNumberStart ?? asm.pageNumber) % 2 === 1 !== (wants === "oddPage")) {
|
|
5163
|
-
asm.cursorY = asm.
|
|
5868
|
+
asm.cursorY = asm.pageTopY();
|
|
5164
5869
|
asm.flushPage(true);
|
|
5165
5870
|
}
|
|
5166
5871
|
}
|
|
@@ -5168,18 +5873,43 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
5168
5873
|
asm.ctx = sectionCtxs[asm.secIdx];
|
|
5169
5874
|
asm.restartPageNumbers(asm.ctx);
|
|
5170
5875
|
asm.pageInSection = 0;
|
|
5171
|
-
asm.cursorY = asm.
|
|
5876
|
+
asm.cursorY = asm.pageTopY();
|
|
5877
|
+
asm.sectionStart = true;
|
|
5172
5878
|
balanceIfEndsContinuous(blockIdx);
|
|
5173
5879
|
}
|
|
5174
5880
|
const block = blocks[blockIdx];
|
|
5881
|
+
const opensSection = asm.sectionStart;
|
|
5882
|
+
const anchored = "float" in block && isOutOfFlowFloat(block.float);
|
|
5883
|
+
if (!anchored) asm.sectionStart = false;
|
|
5175
5884
|
if (asm.pendingPageBreak) {
|
|
5176
5885
|
asm.pendingPageBreak = false;
|
|
5177
5886
|
asm.flushPage(true);
|
|
5178
5887
|
}
|
|
5888
|
+
if (typesetByWord && anchored && asm.pageHasContent() && brokenFor === void 0) {
|
|
5889
|
+
let owner = blockIdx + 1;
|
|
5890
|
+
while (owner < blocks.length) {
|
|
5891
|
+
const b = blocks[owner];
|
|
5892
|
+
if (!("float" in b && isOutOfFlowFloat(b.float))) break;
|
|
5893
|
+
owner++;
|
|
5894
|
+
}
|
|
5895
|
+
const para = blocks[owner];
|
|
5896
|
+
if (para?.kind === "paragraph" && para.resolved.pageBreakBefore) {
|
|
5897
|
+
asm.flushPage();
|
|
5898
|
+
brokenFor = owner;
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5179
5901
|
if (builder && !(block.kind === "paragraph" && block.list)) asm.listStack.length = 0;
|
|
5180
5902
|
if (block.kind === "paragraph") {
|
|
5181
|
-
|
|
5182
|
-
|
|
5903
|
+
const brokeAhead = brokenFor === blockIdx;
|
|
5904
|
+
brokenFor = void 0;
|
|
5905
|
+
const ownBreak = block.resolved.pageBreakBefore && (brokeAhead || asm.pageHasContent());
|
|
5906
|
+
if (!brokeAhead && (block.resolved.pageBreakBefore || block.pageBreakBefore) && asm.pageHasContent()) asm.flushPage();
|
|
5907
|
+
const columnInUse = asm.colHasContent();
|
|
5908
|
+
const opensDocument = opensSection && asm.pages.length === 0 && asm.secIdx === 0;
|
|
5909
|
+
const beforePt = asm.collapseSpacing && opensDocument && block.resolved.spacingBeforeAuto !== void 0 ? 0 : block.spacingBeforePt;
|
|
5910
|
+
asm.cursorY -= asm.openBefore(beforePt, opensSection, ownBreak);
|
|
5911
|
+
const kept = keptTogetherHeight(blocks, blockIdx, asm.ctx.endIndex, typesetByWord);
|
|
5912
|
+
if (kept !== void 0 && columnInUse && asm.exclusions.length === 0 && kept > asm.cursorY - asm.bottomLimit()) asm.advanceColumn();
|
|
5183
5913
|
let pb = block;
|
|
5184
5914
|
const colW = asm.colWidth();
|
|
5185
5915
|
if (reflowParagraph && pb.source && Math.abs(colW - asm.builtWidth()) > .5) pb = reflowParagraph(pb.source, colW);
|
|
@@ -5187,8 +5917,26 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
5187
5917
|
const widths = asm.lineWidthsFor(pb, asm.cursorY);
|
|
5188
5918
|
if (widths) pb = reflowParagraph(pb.source, colW, widths);
|
|
5189
5919
|
}
|
|
5190
|
-
|
|
5191
|
-
|
|
5920
|
+
let borderTopY = asm.cursorY;
|
|
5921
|
+
let borderPage = asm.pages.length;
|
|
5922
|
+
const keptColumnEnd = (from) => {
|
|
5923
|
+
const n = pb.lines.length;
|
|
5924
|
+
if (!pb.resolved.widowControl || n < 2 || asm.exclusions.length > 0) return void 0;
|
|
5925
|
+
let end = asm.firstLineOverflowing(pb, from);
|
|
5926
|
+
if (end >= n) return void 0;
|
|
5927
|
+
if (n - end === 1) end--;
|
|
5928
|
+
if (from === 0 && end === 1) end = 0;
|
|
5929
|
+
if (end <= from) return asm.colHasContent() ? from : from + 1;
|
|
5930
|
+
return end;
|
|
5931
|
+
};
|
|
5932
|
+
const advanceBefore = (lineIdx) => {
|
|
5933
|
+
asm.advanceColumn();
|
|
5934
|
+
if (lineIdx === 0) {
|
|
5935
|
+
borderTopY = asm.cursorY;
|
|
5936
|
+
borderPage = asm.pages.length;
|
|
5937
|
+
}
|
|
5938
|
+
};
|
|
5939
|
+
let columnEnd = keptColumnEnd(0);
|
|
5192
5940
|
let structId;
|
|
5193
5941
|
let markerLblId;
|
|
5194
5942
|
const firstLine = pb.lines[0];
|
|
@@ -5205,13 +5953,21 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
5205
5953
|
let firstLineOfBlock = true;
|
|
5206
5954
|
let pairPending;
|
|
5207
5955
|
for (const [lineIdx, line] of pb.lines.entries()) {
|
|
5208
|
-
if (pb.columnBreakLines?.has(lineIdx) && asm.colHasContent())
|
|
5956
|
+
if (pb.columnBreakLines?.has(lineIdx) && asm.colHasContent()) {
|
|
5957
|
+
advanceBefore(lineIdx);
|
|
5958
|
+
columnEnd = keptColumnEnd(lineIdx);
|
|
5959
|
+
}
|
|
5960
|
+
if (lineIdx === columnEnd && asm.colHasContent()) {
|
|
5961
|
+
advanceBefore(lineIdx);
|
|
5962
|
+
columnEnd = keptColumnEnd(lineIdx);
|
|
5963
|
+
}
|
|
5209
5964
|
const h = computeLineHeight(line, pb.resolved);
|
|
5210
5965
|
let newNotes = asm.lineFootnotes(line);
|
|
5211
5966
|
const addedReserve = (sub) => sub.reduce((sum, x) => sum + x.heightPt, 0) + (asm.pageNotes.length === 0 && sub.length > 0 ? FOOTNOTE_SEPARATOR_HEIGHT : 0);
|
|
5212
5967
|
if (asm.cursorY - h < asm.bottomLimit() + addedReserve(newNotes) && asm.colHasContent()) {
|
|
5213
|
-
|
|
5968
|
+
advanceBefore(lineIdx);
|
|
5214
5969
|
newNotes = asm.lineFootnotes(line);
|
|
5970
|
+
columnEnd = keptColumnEnd(lineIdx);
|
|
5215
5971
|
}
|
|
5216
5972
|
if (newNotes.length > 0) {
|
|
5217
5973
|
asm.noteReserve += addedReserve(newNotes);
|
|
@@ -5291,6 +6047,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
5291
6047
|
}
|
|
5292
6048
|
if (asm.pages.length === borderPage) asm.current.push(...paragraphDecoration(pb.resolved, asm.colLeft() + pb.resolved.indentLeft, asm.colLeft() + asm.colWidth() - pb.resolved.indentRight, borderTopY, asm.cursorY, asm.ctx.pageHeight));
|
|
5293
6049
|
asm.cursorY -= pb.spacingAfterPt;
|
|
6050
|
+
asm.closeAfter(pb.spacingAfterPt);
|
|
5294
6051
|
if (pb.pageBreakAfter) asm.pendingPageBreak = true;
|
|
5295
6052
|
} else if (block.kind === "image") {
|
|
5296
6053
|
const figId = builder ? createFigure(builder, block.altText, "Image") : void 0;
|
|
@@ -5321,12 +6078,13 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
5321
6078
|
emitImageAt(fx, fy, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
|
|
5322
6079
|
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
|
|
5323
6080
|
} else {
|
|
5324
|
-
asm.cursorY -= block.spacingBeforePt;
|
|
6081
|
+
asm.cursorY -= asm.openBefore(block.spacingBeforePt, opensSection);
|
|
5325
6082
|
if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
|
|
5326
6083
|
asm.cursorY -= block.heightPt;
|
|
5327
6084
|
const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, asm.colWidth());
|
|
5328
6085
|
emitImageAt(asm.colLeft() + offset, asm.cursorY + block.heightPt, asm.current);
|
|
5329
6086
|
asm.cursorY -= block.spacingAfterPt;
|
|
6087
|
+
asm.closeAfter(sharedAfter(block));
|
|
5330
6088
|
}
|
|
5331
6089
|
} else if (block.kind === "shape") {
|
|
5332
6090
|
const figId = builder ? createFigure(builder, block.altText, "Shape") : void 0;
|
|
@@ -5337,12 +6095,13 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
5337
6095
|
emitShapeAt(fx, fy - block.heightPt, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
|
|
5338
6096
|
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
|
|
5339
6097
|
} else {
|
|
5340
|
-
asm.cursorY -= block.spacingBeforePt;
|
|
6098
|
+
asm.cursorY -= asm.openBefore(block.spacingBeforePt, opensSection);
|
|
5341
6099
|
if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
|
|
5342
6100
|
asm.cursorY -= block.heightPt;
|
|
5343
6101
|
const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, asm.colWidth());
|
|
5344
6102
|
emitShapeAt(asm.colLeft() + offset, asm.cursorY, asm.current);
|
|
5345
6103
|
asm.cursorY -= block.spacingAfterPt;
|
|
6104
|
+
asm.closeAfter(block.spacingAfterPt);
|
|
5346
6105
|
}
|
|
5347
6106
|
} else if (block.kind === "chart") {
|
|
5348
6107
|
const figId = builder ? createFigure(builder, block.altText, block.figureRole ?? "Chart") : void 0;
|
|
@@ -5355,12 +6114,13 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
5355
6114
|
emitChartAt(fx, fy - block.heightPt, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
|
|
5356
6115
|
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
|
|
5357
6116
|
} else {
|
|
5358
|
-
asm.cursorY -= block.spacingBeforePt;
|
|
6117
|
+
asm.cursorY -= asm.openBefore(block.spacingBeforePt, opensSection);
|
|
5359
6118
|
if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
|
|
5360
6119
|
asm.cursorY -= block.heightPt;
|
|
5361
6120
|
const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, asm.colWidth());
|
|
5362
6121
|
emitChartAt(asm.colLeft() + offset, asm.cursorY, asm.current);
|
|
5363
6122
|
asm.cursorY -= block.spacingAfterPt;
|
|
6123
|
+
asm.closeAfter(sharedAfter(block));
|
|
5364
6124
|
}
|
|
5365
6125
|
} else {
|
|
5366
6126
|
const colCount = block.rows.reduce((max, r) => Math.max(max, r.cells.reduce((s, c) => Math.max(s, c.colStart + c.colSpan), 0)), 0);
|
|
@@ -5432,7 +6192,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
5432
6192
|
asm.flushPage();
|
|
5433
6193
|
if (asm.pages.length === 0) asm.flushPage(true);
|
|
5434
6194
|
for (const d of asm.dynBands) {
|
|
5435
|
-
const cmds = d.render(d.
|
|
6195
|
+
const cmds = d.render(d.pageText, asm.pages.length);
|
|
5436
6196
|
const page = asm.pages[d.pageIdx];
|
|
5437
6197
|
if (d.position === "header") page.commands.unshift(...cmds);
|
|
5438
6198
|
else page.commands.push(...cmds);
|
|
@@ -5623,7 +6383,7 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
|
|
|
5623
6383
|
});
|
|
5624
6384
|
}
|
|
5625
6385
|
}
|
|
5626
|
-
emitCellBorders(out, cell, cellX, rowBottom, row.heightPt, pageHeight, row.rowIdx, row.rowCount, colCount);
|
|
6386
|
+
emitCellBorders(out, cell, cellX, rowBottom, row.heightPt, pageHeight, row.rowIdx, row.rowCount, colCount, row.borderBands);
|
|
5627
6387
|
const diagDown = cell.borders.diagonalDown;
|
|
5628
6388
|
const diagUp = cell.borders.diagonalUp;
|
|
5629
6389
|
if ((diagDown || diagUp) && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
|
|
@@ -5840,17 +6600,19 @@ function splitRowIntoChunks(row, capacity, restCapacity = capacity) {
|
|
|
5840
6600
|
return out.length > 0 ? out : [row];
|
|
5841
6601
|
}
|
|
5842
6602
|
var FILL_SEAM_PT = .07;
|
|
5843
|
-
function emitCellBorders(out, cell, cellX, cellY, rowHeight, pageHeight, rowIdx, rowCount, colCount) {
|
|
6603
|
+
function emitCellBorders(out, cell, cellX, cellY, rowHeight, pageHeight, rowIdx, rowCount, colCount, bands) {
|
|
5844
6604
|
const pushSide = (side, border) => {
|
|
5845
6605
|
if (!border || border.style === "none") return;
|
|
5846
6606
|
const sz = border.width ?? DEFAULT_BORDER_SIZE_EIGHTH * EIGHTH_PT;
|
|
6607
|
+
const lowered = side === "top" ? (bands?.topPt ?? 0) / 2 : 0;
|
|
6608
|
+
const raised = side === "bottom" ? (bands?.bottomPt ?? 0) / 2 : 0;
|
|
5847
6609
|
out.push({
|
|
5848
6610
|
type: "border",
|
|
5849
6611
|
side,
|
|
5850
6612
|
x: pt(cellX),
|
|
5851
|
-
y: pt(pageHeight - cellY - rowHeight),
|
|
6613
|
+
y: pt(pageHeight - cellY - rowHeight + lowered),
|
|
5852
6614
|
width: pt(cell.paintWidthPt ?? cell.widthPt),
|
|
5853
|
-
height: pt(rowHeight),
|
|
6615
|
+
height: pt(rowHeight - lowered - raised),
|
|
5854
6616
|
borderSizePt: sz,
|
|
5855
6617
|
borderColorHex: border.colorHex ?? "000000",
|
|
5856
6618
|
...border.style !== "single" ? { borderStyle: border.style } : {}
|