reamkit 1.16.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/core/bidi/segments.js +1 -2
- package/dist/esm/core/converter/project.js +5 -1
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +526 -8
- package/dist/esm/core/drawingml/chart-geometry.d.ts +8 -12
- package/dist/esm/core/drawingml/chart-geometry.js +58 -9
- package/dist/esm/core/drawingml/chart-parser.js +44 -5
- package/dist/esm/core/drawingml/colors.d.ts +1 -1
- package/dist/esm/core/drawingml/colors.js +27 -1
- package/dist/esm/core/drawingml/preset-geometry.js +233 -0
- package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
- package/dist/esm/core/drawingml/shape-render.js +116 -3
- package/dist/esm/core/drawingml/theme-parser.d.ts +10 -0
- package/dist/esm/core/drawingml/theme-parser.js +19 -1
- package/dist/esm/core/font/measure.d.ts +5 -0
- package/dist/esm/core/font/measure.js +21 -1
- package/dist/esm/core/images.d.ts +2 -2
- package/dist/esm/core/images.js +219 -0
- package/dist/esm/core/ir/flow.d.ts +23 -1
- package/dist/esm/core/lzw.d.ts +17 -0
- package/dist/esm/core/lzw.js +83 -0
- package/dist/esm/core/metafile/emf.d.ts +16 -0
- package/dist/esm/core/metafile/emf.js +635 -0
- package/dist/esm/core/metafile/picture.d.ts +106 -0
- package/dist/esm/core/metafile/picture.js +55 -0
- package/dist/esm/core/metafile/wmf.d.ts +9 -0
- package/dist/esm/core/metafile/wmf.js +371 -0
- package/dist/esm/core/numbering/apply.d.ts +5 -3
- package/dist/esm/core/numbering/apply.js +42 -11
- package/dist/esm/core/numbering/index.js +2 -0
- package/dist/esm/core/numbering/state.d.ts +13 -1
- package/dist/esm/core/numbering/state.js +256 -15
- package/dist/esm/core/po-helpers.js +16 -1
- package/dist/esm/core/style-cascade/resolver.js +66 -11
- package/dist/esm/core/style-cascade/table.js +35 -11
- package/dist/esm/core/style-cascade/types.d.ts +42 -1
- package/dist/esm/core/style-cascade/types.js +4 -0
- package/dist/esm/core/tiff.d.ts +20 -0
- package/dist/esm/core/tiff.js +339 -0
- package/dist/esm/core/vector.d.ts +22 -0
- package/dist/esm/excel/print-model.js +4 -2
- package/dist/esm/excel/sheet-to-flow.js +10 -8
- package/dist/esm/excel/worksheet-parser.js +1 -1
- package/dist/esm/html/html-writer.js +20 -5
- package/dist/esm/layout/page-doc.d.ts +107 -4
- package/dist/esm/layout/styled-layout.d.ts +44 -2
- package/dist/esm/layout/styled-layout.js +2330 -283
- package/dist/esm/pdf/shading.js +76 -6
- package/dist/esm/pdf/styled-page-emitter.js +294 -45
- package/dist/esm/pdf/vector-graphics.d.ts +29 -2
- package/dist/esm/pdf/vector-graphics.js +96 -12
- package/dist/esm/pdf-reader/image-decode.js +7 -75
- package/dist/esm/pptx/pptx-reader.js +1 -0
- package/dist/esm/pptx/slide-parser.d.ts +21 -4
- package/dist/esm/pptx/slide-parser.js +86 -23
- package/dist/esm/svg/svg-writer.js +2 -2
- package/dist/esm/word/document-parser.d.ts +96 -7
- package/dist/esm/word/document-parser.js +540 -116
- package/dist/esm/word/docx-reader.js +135 -43
- package/dist/esm/word/docx-writer.js +6 -1
- package/dist/esm/word/drawing-parser.d.ts +58 -5
- package/dist/esm/word/drawing-parser.js +1632 -63
- package/dist/esm/word/index.d.ts +4 -4
- package/dist/esm/word/numbering-parser.d.ts +2 -1
- package/dist/esm/word/numbering-parser.js +107 -21
- package/dist/esm/word/paragraph-properties.d.ts +11 -1
- package/dist/esm/word/paragraph-properties.js +219 -10
- package/dist/esm/word/run-properties.js +45 -1
- package/dist/esm/word/settings-parser.d.ts +24 -0
- package/dist/esm/word/settings-parser.js +31 -5
- package/dist/esm/word/shading.d.ts +11 -0
- package/dist/esm/word/shading.js +64 -0
- package/dist/esm/word/styles-parser.js +8 -2
- package/dist/esm/word/table-parser.d.ts +9 -1
- package/dist/esm/word/table-parser.js +182 -30
- package/package.json +1 -1
|
@@ -4,23 +4,26 @@ import { createFontMeasure } from "../core/font/measure.js";
|
|
|
4
4
|
import { halfPtToPt, pt } from "../core/ir/units.js";
|
|
5
5
|
import { ResourceStore } from "../core/ir/resources.js";
|
|
6
6
|
import { prepareImage } from "../core/images.js";
|
|
7
|
+
import { PathBuilder, flipTransform } from "../core/vector.js";
|
|
8
|
+
import { isEmf, readEmf } from "../core/metafile/emf.js";
|
|
9
|
+
import { isWmf, readWmf } from "../core/metafile/wmf.js";
|
|
7
10
|
import { segmentLevels } from "../core/bidi/segments.js";
|
|
8
11
|
import { analyzeString, hasBidiCharacters } from "../core/bidi/index.js";
|
|
9
12
|
import { FORCED_BREAK, breakLines } from "../core/line-breaker/knuth-plass.js";
|
|
10
13
|
import { greedyBreakLines } from "../core/line-breaker/greedy.js";
|
|
11
14
|
import { cjkBreakBetween, splitCjkSegment } from "../core/line-breaker/cjk.js";
|
|
12
15
|
import "../core/line-breaker/index.js";
|
|
13
|
-
import { applyNumbering, applyNumberingToHeadersFooters } from "../core/numbering/apply.js";
|
|
14
16
|
import { DEFAULT_RESOLVED_PARAGRAPH, DEFAULT_RESOLVED_RUN } from "../core/style-cascade/types.js";
|
|
15
17
|
import { resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
|
|
16
18
|
import "../core/style-cascade/index.js";
|
|
17
|
-
import {
|
|
19
|
+
import { applyNumbering, applyNumberingToHeadersFooters } from "../core/numbering/apply.js";
|
|
20
|
+
import "../core/numbering/index.js";
|
|
18
21
|
import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
|
|
19
22
|
import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
|
|
20
23
|
import { buildChartScene, legendSeriesName } from "../core/drawingml/chart-geometry.js";
|
|
21
24
|
import { layoutMath, mathGlyphSegments, variantStyle } from "./math-layout.js";
|
|
22
25
|
import { rectPath } from "../core/drawingml/preset-geometry.js";
|
|
23
|
-
import { buildShapePaths, buildShapeTransform, buildStroke, gradientToSolid } from "../core/drawingml/shape-render.js";
|
|
26
|
+
import { buildShapePaths, buildShapeTransform, buildStroke, gradientToSolid, lineEndPaths } from "../core/drawingml/shape-render.js";
|
|
24
27
|
import { StructTreeBuilder } from "../pdf/struct-tree.js";
|
|
25
28
|
//#region src/layout/styled-layout.ts
|
|
26
29
|
function parsePdfAProfile(pdfA) {
|
|
@@ -33,6 +36,14 @@ function parsePdfAProfile(pdfA) {
|
|
|
33
36
|
version: part === 1 ? "1.4" : "1.7"
|
|
34
37
|
};
|
|
35
38
|
}
|
|
39
|
+
/** §17.6.4 — how heavy Word draws the rule between columns. */
|
|
40
|
+
var COLUMN_SEPARATOR_PT = .5;
|
|
41
|
+
/**
|
|
42
|
+
* How far a space may be squeezed below its natural width — the shrink the
|
|
43
|
+
* line breaker offers when it weighs a line, and therefore the shrink the
|
|
44
|
+
* emitter owes it back when it draws one.
|
|
45
|
+
*/
|
|
46
|
+
var GLUE_SHRINK_RATIO = .3;
|
|
36
47
|
var TWIP_TO_PT = 1 / 20;
|
|
37
48
|
var EIGHTH_PT = 1 / 8;
|
|
38
49
|
var DEFAULT_CELL_PADDING_TWIPS = 108;
|
|
@@ -40,11 +51,41 @@ var DEFAULT_BORDER_SIZE_EIGHTH = 4;
|
|
|
40
51
|
var FOOTNOTE_RULE_PT = .75;
|
|
41
52
|
var FOOTNOTE_RULE_GAP_ABOVE = 4;
|
|
42
53
|
var FOOTNOTE_SEPARATOR_HEIGHT = 10;
|
|
54
|
+
var LINE_NUMBER_GAP_PT = 18;
|
|
43
55
|
var FLOAT_TEXT_GAP = 6;
|
|
44
56
|
var MIN_WRAP_WIDTH = 36;
|
|
45
57
|
function isOutOfFlowFloat(f) {
|
|
46
58
|
return f !== void 0 && f.wrap !== "topAndBottom";
|
|
47
59
|
}
|
|
60
|
+
/** The float's left edge, resolved from its horizontal anchor (§20.4.3.3). */
|
|
61
|
+
function floatXIn(frame, f, widthPt) {
|
|
62
|
+
const h = f.posH;
|
|
63
|
+
if (!h) return frame.columnLeft;
|
|
64
|
+
const textRight = frame.marginLeft + frame.contentWidth;
|
|
65
|
+
const pageRight = frame.pageLeft + frame.pageWidth;
|
|
66
|
+
const base = h.relativeFrom === "page" || h.relativeFrom === "leftMargin" ? frame.pageLeft : h.relativeFrom === "column" ? frame.columnLeft : h.relativeFrom === "rightMargin" ? textRight : frame.marginLeft;
|
|
67
|
+
const span = h.relativeFrom === "page" ? frame.pageWidth : h.relativeFrom === "column" ? frame.columnWidth : h.relativeFrom === "leftMargin" ? frame.marginLeft - frame.pageLeft : h.relativeFrom === "rightMargin" ? pageRight - textRight : frame.contentWidth;
|
|
68
|
+
if (h.align === "center") return base + (span - widthPt) / 2;
|
|
69
|
+
if (h.align === "right") return base + span - widthPt;
|
|
70
|
+
return base + (h.offsetPt ?? 0);
|
|
71
|
+
}
|
|
72
|
+
/** The float's TOP, y-up, resolved from its vertical anchor (§20.4.3.4). */
|
|
73
|
+
function floatTopYUpIn(frame, f, heightPt = 0) {
|
|
74
|
+
const v = f.posV;
|
|
75
|
+
if (!v) return frame.anchorY;
|
|
76
|
+
if (v.align !== void 0) {
|
|
77
|
+
const page = v.relativeFrom === "page";
|
|
78
|
+
const topYUp = page ? frame.pageTopYUp : frame.marginTopYUp;
|
|
79
|
+
const bottomYUp = page ? frame.pageBottomYUp : frame.marginBottomYUp;
|
|
80
|
+
if (v.align === "top") return topYUp;
|
|
81
|
+
if (v.align === "bottom") return bottomYUp + heightPt;
|
|
82
|
+
return bottomYUp + (topYUp - bottomYUp + heightPt) / 2;
|
|
83
|
+
}
|
|
84
|
+
if (v.relativeFrom === "page" || v.relativeFrom === "topMargin") return frame.pageTopYUp - (v.offsetPt ?? 0);
|
|
85
|
+
if (v.relativeFrom === "bottomMargin") return frame.marginBottomYUp - (v.offsetPt ?? 0);
|
|
86
|
+
if (v.relativeFrom === "margin") return frame.marginTopYUp - (v.offsetPt ?? 0);
|
|
87
|
+
return frame.anchorY - (v.offsetPt ?? 0);
|
|
88
|
+
}
|
|
48
89
|
var FOOTNOTE_RULE_WIDTH = 144;
|
|
49
90
|
function assignNoteNumbers(body, commentIds) {
|
|
50
91
|
const footnotes = /* @__PURE__ */ new Map();
|
|
@@ -257,21 +298,49 @@ function commentTailBlocks(comment, n, opts) {
|
|
|
257
298
|
*/
|
|
258
299
|
function layoutStyledDocument(body, options) {
|
|
259
300
|
const sectionList = resolveSectionList(body, options);
|
|
260
|
-
const noteAssigned = assignNoteNumbers(applyNumbering(body, options.numbering), options.comments ? new Set(options.comments.keys()) : void 0);
|
|
301
|
+
const noteAssigned = assignNoteNumbers(applyNumbering(body, options.numbering, options.styles), options.comments ? new Set(options.comments.keys()) : void 0);
|
|
261
302
|
const numberedBody = noteAssigned.body;
|
|
262
|
-
const numberedHeadersFooters = applyNumberingToHeadersFooters(options.headersFooters, options.numbering);
|
|
303
|
+
const numberedHeadersFooters = applyNumberingToHeadersFooters(options.headersFooters, options.numbering, options.styles);
|
|
263
304
|
const pdfaProfile = options.pdfA ? parsePdfAProfile(options.pdfA) : void 0;
|
|
264
305
|
const tagged = options.tagged === true || options.pdfUA === true || (pdfaProfile?.tagged ?? false);
|
|
265
306
|
const structBuilder = tagged ? new StructTreeBuilder() : void 0;
|
|
266
|
-
const fontResources = collectFontResources(numberedBody, numberedHeadersFooters, options);
|
|
267
307
|
const imageResources = collectImageResources(numberedBody, numberedHeadersFooters, options);
|
|
268
|
-
const
|
|
308
|
+
const fontResources = collectFontResources(numberedBody, numberedHeadersFooters, options, imageResources);
|
|
309
|
+
const sectionCtxs = sectionList.map((s) => buildSectionContext(s, options, numberedHeadersFooters, fontResources, imageResources));
|
|
269
310
|
let sectionIdx = 0;
|
|
311
|
+
const bodyFrames = frameGroups(numberedBody);
|
|
270
312
|
const blocks = numberedBody.map((el, idx) => {
|
|
271
313
|
while (sectionIdx < sectionCtxs.length - 1 && idx >= sectionCtxs[sectionIdx].endIndex) sectionIdx++;
|
|
272
314
|
const ctx = sectionCtxs[sectionIdx];
|
|
273
|
-
|
|
315
|
+
const width = ctx.columns ? ctx.columns[0].widthPt : ctx.contentWidth;
|
|
316
|
+
const box = {
|
|
317
|
+
pageWidthPt: ctx.pageWidth,
|
|
318
|
+
pageHeightPt: ctx.pageHeight,
|
|
319
|
+
marginLeftPt: ctx.marginLeft,
|
|
320
|
+
marginRightPt: ctx.pageWidth - ctx.marginLeft - ctx.contentWidth,
|
|
321
|
+
marginTopPt: ctx.marginTop,
|
|
322
|
+
marginBottomPt: ctx.marginBottom
|
|
323
|
+
};
|
|
324
|
+
const group = bodyFrames.starts.get(idx);
|
|
325
|
+
if (group) return layoutFrameBlock(group, ctx.options ?? options, fontResources, imageResources, width);
|
|
326
|
+
if (bodyFrames.continued.has(idx)) return emptyBlock(options);
|
|
327
|
+
return layoutBodyElement(el, ctx.options ?? options, fontResources, imageResources, width, ctx.pageContentHeight, box);
|
|
274
328
|
});
|
|
329
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
330
|
+
const here = blocks[i];
|
|
331
|
+
const next = blocks[i + 1];
|
|
332
|
+
if (here?.kind !== "paragraph" || next?.kind !== "paragraph") continue;
|
|
333
|
+
if (here.source?.properties.styleId !== next.source?.properties.styleId) continue;
|
|
334
|
+
if (here.resolved.contextualSpacing) blocks[i] = {
|
|
335
|
+
...here,
|
|
336
|
+
spacingAfterPt: 0
|
|
337
|
+
};
|
|
338
|
+
if (next.resolved.contextualSpacing) blocks[i + 1] = {
|
|
339
|
+
...next,
|
|
340
|
+
spacingBeforePt: 0
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
flowLinkedTextBoxes(blocks);
|
|
275
344
|
const noteBlockCache = /* @__PURE__ */ new Map();
|
|
276
345
|
const notePlan = options.footnotes && noteAssigned.footnotes.size > 0 ? {
|
|
277
346
|
numbers: noteAssigned.footnotes,
|
|
@@ -286,7 +355,7 @@ function layoutStyledDocument(body, options) {
|
|
|
286
355
|
const n = noteAssigned.footnotes.get(id);
|
|
287
356
|
let laid = void 0;
|
|
288
357
|
if (content && n !== void 0 && !noteAssigned.deferredFootnotes.includes(id)) {
|
|
289
|
-
const noteBlocks = substituteNoteNumber(content, n).map((el) => layoutBodyElement(el, options, fontResources, imageResources, sectionCtx.contentWidth, sectionCtx.pageContentHeight));
|
|
358
|
+
const noteBlocks = substituteNoteNumber(content, n).map((el) => layoutBodyElement(el, sectionCtx.options ?? options, fontResources, imageResources, sectionCtx.contentWidth, sectionCtx.pageContentHeight));
|
|
290
359
|
laid = {
|
|
291
360
|
blocks: noteBlocks,
|
|
292
361
|
heightPt: noteBlocks.reduce((sum, b) => sum + (b.kind === "paragraph" ? b.spacingBeforePt + b.heightPt + b.spacingAfterPt : 0), 0)
|
|
@@ -314,7 +383,24 @@ function layoutStyledDocument(body, options) {
|
|
|
314
383
|
n
|
|
315
384
|
});
|
|
316
385
|
}
|
|
317
|
-
|
|
386
|
+
if (tailNotes.length > 0) blocks.push(layoutShapeBlock({
|
|
387
|
+
width: pt(FOOTNOTE_RULE_WIDTH),
|
|
388
|
+
height: pt(FOOTNOTE_RULE_PT),
|
|
389
|
+
geometry: {
|
|
390
|
+
kind: "preset",
|
|
391
|
+
preset: "rect",
|
|
392
|
+
adjust: /* @__PURE__ */ new Map()
|
|
393
|
+
},
|
|
394
|
+
fill: {
|
|
395
|
+
kind: "solid",
|
|
396
|
+
colorHex: "000000"
|
|
397
|
+
},
|
|
398
|
+
paragraphProperties: {
|
|
399
|
+
spacingBefore: pt(FOOTNOTE_SEPARATOR_HEIGHT),
|
|
400
|
+
spacingAfter: pt(FOOTNOTE_RULE_GAP_ABOVE)
|
|
401
|
+
}
|
|
402
|
+
}, options, fontResources, imageResources, lastCtx.contentWidth));
|
|
403
|
+
for (const note of tailNotes.sort((a, b) => a.n - b.n)) for (const el of substituteNoteNumber(note.content, note.n)) blocks.push(layoutBodyElement(el, lastCtx.options ?? options, fontResources, imageResources, lastCtx.contentWidth, lastCtx.pageContentHeight));
|
|
318
404
|
const commentNums = noteAssigned.comments;
|
|
319
405
|
const threadDepth = (id) => {
|
|
320
406
|
let depth = 0;
|
|
@@ -346,7 +432,7 @@ function layoutStyledDocument(body, options) {
|
|
|
346
432
|
const commentNotes = options.commentAnnotations && options.comments ? buildCommentNotes(noteAssigned.comments, options.comments) : void 0;
|
|
347
433
|
const reflowParagraph = (paragraph, width, widths) => layoutParagraphBlock(paragraph, options, fontResources, imageResources, width, widths);
|
|
348
434
|
return {
|
|
349
|
-
pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph),
|
|
435
|
+
pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph, options.pageBackgroundFill?.imageResource !== void 0 ? imageResources.get(options.pageBackgroundFill.imageResource)?.resourceName : void 0),
|
|
350
436
|
resources: options.resources ?? new ResourceStore(),
|
|
351
437
|
fontResources,
|
|
352
438
|
imageResources,
|
|
@@ -367,14 +453,16 @@ function resolvePageDimensions(options, section) {
|
|
|
367
453
|
const sectionRight = section?.margins?.right !== void 0 ? section.margins.right : void 0;
|
|
368
454
|
const sectionTop = section?.margins?.top !== void 0 ? section.margins.top : void 0;
|
|
369
455
|
const sectionBottom = section?.margins?.bottom !== void 0 ? section.margins.bottom : void 0;
|
|
456
|
+
const gutter = section?.margins?.gutter ?? 0;
|
|
457
|
+
const atTop = options.gutterAtTop === true;
|
|
370
458
|
const headerOffsetPt = section?.margins?.header ?? 720 * TWIP_TO_PT;
|
|
371
459
|
const footerOffsetPt = section?.margins?.footer ?? 720 * TWIP_TO_PT;
|
|
372
460
|
return {
|
|
373
461
|
pageWidth: options.pageWidth ?? sectionPageWidth ?? 595,
|
|
374
462
|
pageHeight: options.pageHeight ?? sectionPageHeight ?? 842,
|
|
375
|
-
marginLeft: options.marginLeft ?? sectionLeft ?? 72,
|
|
463
|
+
marginLeft: (options.marginLeft ?? sectionLeft ?? 72) + (atTop ? 0 : gutter),
|
|
376
464
|
marginRight: options.marginRight ?? sectionRight ?? 72,
|
|
377
|
-
marginTop: options.marginTop ?? sectionTop ?? 72,
|
|
465
|
+
marginTop: (options.marginTop ?? sectionTop ?? 72) + (atTop ? gutter : 0),
|
|
378
466
|
marginBottom: options.marginBottom ?? sectionBottom ?? 72,
|
|
379
467
|
headerOffsetPt,
|
|
380
468
|
footerOffsetPt
|
|
@@ -394,13 +482,32 @@ function resolveSectionList(body, options) {
|
|
|
394
482
|
endIndex: body.length
|
|
395
483
|
}];
|
|
396
484
|
}
|
|
397
|
-
function buildSectionContext(section, options, headersFooters, fontResources) {
|
|
485
|
+
function buildSectionContext(section, options, headersFooters, fontResources, imageResources) {
|
|
398
486
|
const dims = resolvePageDimensions(options, section.properties);
|
|
399
487
|
const contentWidth = dims.pageWidth - dims.marginLeft - dims.marginRight;
|
|
400
|
-
const
|
|
401
|
-
|
|
488
|
+
const secOptions = section.properties.gridLinePitchPt !== void 0 ? {
|
|
489
|
+
...options,
|
|
490
|
+
gridLinePitchPt: section.properties.gridLinePitchPt
|
|
491
|
+
} : options;
|
|
492
|
+
const relativeBox = {
|
|
493
|
+
pageWidthPt: dims.pageWidth,
|
|
494
|
+
pageHeightPt: dims.pageHeight,
|
|
495
|
+
marginLeftPt: dims.marginLeft,
|
|
496
|
+
marginRightPt: dims.marginRight,
|
|
497
|
+
marginTopPt: dims.marginTop,
|
|
498
|
+
marginBottomPt: dims.marginBottom
|
|
499
|
+
};
|
|
500
|
+
const headerSet = layoutHeaderSet(section.properties, headersFooters, secOptions, fontResources, imageResources, contentWidth, dims.marginLeft, dims.pageHeight, dims.headerOffsetPt, {
|
|
501
|
+
top: dims.marginTop,
|
|
502
|
+
bottom: dims.marginBottom
|
|
503
|
+
}, relativeBox);
|
|
504
|
+
const footerSet = layoutFooterSet(section.properties, headersFooters, secOptions, fontResources, imageResources, contentWidth, dims.marginLeft, dims.pageHeight, dims.footerOffsetPt, {
|
|
505
|
+
top: dims.marginTop,
|
|
506
|
+
bottom: dims.marginBottom
|
|
507
|
+
}, relativeBox);
|
|
402
508
|
const columns = buildColumnGeometry(section.properties.columns, contentWidth);
|
|
403
|
-
const
|
|
509
|
+
const headerHeight = Math.max(headerSet.default.heightPt ?? 0, section.properties.titlePg === true ? headerSet.first.heightPt ?? 0 : 0, section.properties.evenAndOddHeaders === true ? headerSet.even.heightPt ?? 0 : 0);
|
|
510
|
+
const headerBottom = headerHeight > 0 ? dims.headerOffsetPt + headerHeight : 0;
|
|
404
511
|
const marginTop = Math.max(dims.marginTop, headerBottom);
|
|
405
512
|
return {
|
|
406
513
|
endIndex: section.endIndex,
|
|
@@ -416,7 +523,9 @@ function buildSectionContext(section, options, headersFooters, fontResources) {
|
|
|
416
523
|
headerSet,
|
|
417
524
|
footerSet,
|
|
418
525
|
titlePg: section.properties.titlePg === true,
|
|
419
|
-
evenAndOddHeaders: section.properties.evenAndOddHeaders === true
|
|
526
|
+
evenAndOddHeaders: section.properties.evenAndOddHeaders === true,
|
|
527
|
+
continuous: section.properties.sectionStart === "continuous",
|
|
528
|
+
options: secOptions
|
|
420
529
|
};
|
|
421
530
|
}
|
|
422
531
|
function buildColumnGeometry(cols, contentWidth) {
|
|
@@ -451,7 +560,7 @@ function markPagination(cmds) {
|
|
|
451
560
|
artifact: "pagination"
|
|
452
561
|
}));
|
|
453
562
|
}
|
|
454
|
-
function layoutHeaderSet(section, headersFooters, options, fontResources, contentWidth, marginLeft, pageHeight, headerOffsetPt) {
|
|
563
|
+
function layoutHeaderSet(section, headersFooters, options, fontResources, imageResources, contentWidth, marginLeft, pageHeight, headerOffsetPt, bandMargins, relativeBox) {
|
|
455
564
|
const band = (type) => {
|
|
456
565
|
const ref = refByType(section.headers, type);
|
|
457
566
|
if (!ref) return { commands: [] };
|
|
@@ -459,9 +568,9 @@ function layoutHeaderSet(section, headersFooters, options, fontResources, conten
|
|
|
459
568
|
if (!content) return { commands: [] };
|
|
460
569
|
let measured = 0;
|
|
461
570
|
const render = (c) => {
|
|
462
|
-
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth);
|
|
571
|
+
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth, imageResources, relativeBox);
|
|
463
572
|
measured = blocksHeight(blocks);
|
|
464
|
-
return markPagination(drawBlocksSequentially(blocks, marginLeft, pageHeight - headerOffsetPt, pageHeight));
|
|
573
|
+
return markPagination(drawBlocksSequentially(blocks, marginLeft, pageHeight - headerOffsetPt, pageHeight, contentWidth, void 0, bandMargins));
|
|
465
574
|
};
|
|
466
575
|
if (contentHasPageFields(content)) {
|
|
467
576
|
render(substitutePageFields(content, 1, 1));
|
|
@@ -482,19 +591,37 @@ function layoutHeaderSet(section, headersFooters, options, fontResources, conten
|
|
|
482
591
|
even: band("even")
|
|
483
592
|
};
|
|
484
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* §17.6.4 — the height each column of a BALANCED band is given: the band's
|
|
596
|
+
* share, but never more than the page has room for, and never so little that
|
|
597
|
+
* the band could not fit the columns at all.
|
|
598
|
+
*
|
|
599
|
+
* @param bandHeightPt The band's whole laid-out height.
|
|
600
|
+
* @param colCount How many columns it has to share.
|
|
601
|
+
* @param roomPt What the page has left below the band's top.
|
|
602
|
+
* @returns The per-column height, or 0 when the band should just fill.
|
|
603
|
+
*/
|
|
604
|
+
function balancedColumnHeight(bandHeightPt, colCount, roomPt) {
|
|
605
|
+
if (bandHeightPt <= 0 || colCount < 2 || roomPt <= 0) return 0;
|
|
606
|
+
if (bandHeightPt > roomPt * colCount) return 0;
|
|
607
|
+
return Math.min(roomPt, bandHeightPt / colCount);
|
|
608
|
+
}
|
|
485
609
|
/** Total laid-out height of a run of blocks, paragraph spacing included. */
|
|
486
610
|
function blocksHeight(blocks) {
|
|
487
|
-
return blocks.reduce((sum, b) =>
|
|
611
|
+
return blocks.reduce((sum, b) => {
|
|
612
|
+
if (b.kind !== "paragraph" && b.kind !== "table" && isOutOfFlowFloat(b.float)) return sum;
|
|
613
|
+
return sum + (b.kind === "paragraph" ? b.spacingBeforePt + b.heightPt + b.spacingAfterPt : b.heightPt);
|
|
614
|
+
}, 0);
|
|
488
615
|
}
|
|
489
|
-
function layoutFooterSet(section, headersFooters, options, fontResources, contentWidth, marginLeft, pageHeight, footerOffsetPt) {
|
|
616
|
+
function layoutFooterSet(section, headersFooters, options, fontResources, imageResources, contentWidth, marginLeft, pageHeight, footerOffsetPt, bandMargins, relativeBox) {
|
|
490
617
|
const band = (type) => {
|
|
491
618
|
const ref = refByType(section.footers, type);
|
|
492
619
|
if (!ref) return { commands: [] };
|
|
493
620
|
const content = headersFooters.get(ref.relationshipId);
|
|
494
621
|
if (!content) return { commands: [] };
|
|
495
622
|
const render = (c) => {
|
|
496
|
-
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth);
|
|
497
|
-
return markPagination(drawBlocksSequentially(blocks, marginLeft, footerOffsetPt + blocks
|
|
623
|
+
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth, imageResources, relativeBox);
|
|
624
|
+
return markPagination(drawBlocksSequentially(blocks, marginLeft, footerOffsetPt + blocksHeight(blocks), pageHeight, contentWidth, void 0, bandMargins));
|
|
498
625
|
};
|
|
499
626
|
if (contentHasPageFields(content)) return {
|
|
500
627
|
commands: [],
|
|
@@ -530,7 +657,7 @@ function substitutePageFields(content, pageNumber, totalPages) {
|
|
|
530
657
|
}
|
|
531
658
|
function pickBand(set, band) {
|
|
532
659
|
const has = (e) => e.commands.length > 0 || e.renderDynamic !== void 0;
|
|
533
|
-
if (band === "first") return
|
|
660
|
+
if (band === "first") return set.first;
|
|
534
661
|
if (band === "even") return has(set.even) ? set.even : set.default;
|
|
535
662
|
return set.default;
|
|
536
663
|
}
|
|
@@ -539,24 +666,329 @@ function bandForPage(pageInSection, globalPageIdx, titlePg, evenAndOddHeaders) {
|
|
|
539
666
|
if (evenAndOddHeaders && (globalPageIdx + 1) % 2 === 0) return "even";
|
|
540
667
|
return "default";
|
|
541
668
|
}
|
|
542
|
-
|
|
543
|
-
|
|
669
|
+
/**
|
|
670
|
+
* §17.3.1.11 — the runs of consecutive paragraphs that carry the SAME frame.
|
|
671
|
+
* Word treats them as one floating text frame, so they lay out together in one
|
|
672
|
+
* box; the entries after the first become empty blocks, which keeps every
|
|
673
|
+
* caller's element indexing (and its section bounds) intact.
|
|
674
|
+
*
|
|
675
|
+
* @param elements The body elements, in order.
|
|
676
|
+
* @returns `starts`: the index each frame begins at → its paragraphs;
|
|
677
|
+
* `continued`: the indices swallowed by a frame that began earlier.
|
|
678
|
+
*/
|
|
679
|
+
function frameGroups(elements) {
|
|
680
|
+
const starts = /* @__PURE__ */ new Map();
|
|
681
|
+
const continued = /* @__PURE__ */ new Set();
|
|
682
|
+
const key = (p) => p.properties.frame ? JSON.stringify(p.properties.frame) : void 0;
|
|
683
|
+
let i = 0;
|
|
684
|
+
while (i < elements.length) {
|
|
685
|
+
const el = elements[i];
|
|
686
|
+
const k = el.kind === "paragraph" ? key(el.paragraph) : void 0;
|
|
687
|
+
if (k === void 0) {
|
|
688
|
+
i++;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
const members = [el.paragraph];
|
|
692
|
+
let j = i + 1;
|
|
693
|
+
while (j < elements.length) {
|
|
694
|
+
const next = elements[j];
|
|
695
|
+
if (next.kind !== "paragraph" || key(next.paragraph) !== k) break;
|
|
696
|
+
members.push(next.paragraph);
|
|
697
|
+
continued.add(j);
|
|
698
|
+
j++;
|
|
699
|
+
}
|
|
700
|
+
starts.set(i, members);
|
|
701
|
+
i = j;
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
starts,
|
|
705
|
+
continued
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
/** A block that occupies no space at all — the tail of a frame group. */
|
|
709
|
+
function emptyBlock(options) {
|
|
710
|
+
return {
|
|
711
|
+
kind: "paragraph",
|
|
712
|
+
resolved: resolveParagraphProperties({}, options.styles),
|
|
713
|
+
lines: [],
|
|
714
|
+
heightPt: 0,
|
|
715
|
+
spacingBeforePt: 0,
|
|
716
|
+
spacingAfterPt: 0
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* A text frame as the floating box it is: the paragraphs inside it lay out at
|
|
721
|
+
* the frame's width, the box takes the height the frame states or the text
|
|
722
|
+
* needs, and the body flows past it as its `w:wrap` says.
|
|
723
|
+
*
|
|
724
|
+
* @param paragraphs The frame's paragraphs, in order.
|
|
725
|
+
* @param options Render options.
|
|
726
|
+
* @param fontResources Fonts by variant.
|
|
727
|
+
* @param imageResources Images by resource id.
|
|
728
|
+
* @param contentWidth The width available where the frame is anchored.
|
|
729
|
+
* @returns The laid-out frame.
|
|
730
|
+
*/
|
|
731
|
+
function layoutFrameBlock(paragraphs, options, fontResources, imageResources, contentWidth) {
|
|
732
|
+
const frame = paragraphs[0].properties.frame;
|
|
733
|
+
const exact = frame.heightRule === "exact" && frame.heightPt !== void 0;
|
|
734
|
+
const raw = (widthPt, pin) => layoutShapeBlock(frameShape(frame, paragraphs, widthPt, pin), options, fontResources, imageResources, contentWidth);
|
|
735
|
+
const atLeast = !exact && frame.heightPt !== void 0 && frame.heightRule !== "auto" ? frame.heightPt : 0;
|
|
736
|
+
const build = (widthPt) => {
|
|
737
|
+
const laid = raw(widthPt, exact);
|
|
738
|
+
return laid.kind === "shape" && atLeast > laid.heightPt ? raw(widthPt, true) : laid;
|
|
739
|
+
};
|
|
740
|
+
if (frame.widthPt !== void 0) return build(frame.widthPt);
|
|
741
|
+
const measured = build(contentWidth);
|
|
742
|
+
const natural = measured.kind === "shape" ? Math.max(1, ...measured.textLines.map((l) => l.contentWidthPt)) : contentWidth;
|
|
743
|
+
return build(Math.min(contentWidth, natural));
|
|
744
|
+
}
|
|
745
|
+
var MAX_FIT_PASSES = 4;
|
|
746
|
+
function scaleTextBody(text, k) {
|
|
747
|
+
return {
|
|
748
|
+
...text,
|
|
749
|
+
content: text.content.map((el) => el.kind === "paragraph" ? {
|
|
750
|
+
...el,
|
|
751
|
+
paragraph: {
|
|
752
|
+
...el.paragraph,
|
|
753
|
+
runs: el.paragraph.runs.map((run) => ({
|
|
754
|
+
...run,
|
|
755
|
+
properties: {
|
|
756
|
+
...run.properties,
|
|
757
|
+
fontSizePt: pt((run.properties.fontSizePt ?? 11) * k)
|
|
758
|
+
}
|
|
759
|
+
}))
|
|
760
|
+
}
|
|
761
|
+
} : el)
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
function frameShape(frame, paragraphs, widthPt, exact) {
|
|
765
|
+
const lead = paragraphs[0]?.properties;
|
|
766
|
+
const edge = lead?.borders?.top ?? lead?.borders?.left;
|
|
767
|
+
const vertical = lead?.textDirection === "btLr" ? "vert270" : lead?.textDirection === "tbRl" ? "vert" : void 0;
|
|
768
|
+
const painted = lead?.shading !== void 0 || edge !== void 0 && edge.style !== "none";
|
|
769
|
+
return {
|
|
770
|
+
width: pt(widthPt),
|
|
771
|
+
height: pt(exact ? frame.heightPt ?? 0 : 0),
|
|
772
|
+
geometry: painted ? {
|
|
773
|
+
kind: "preset",
|
|
774
|
+
preset: "rect",
|
|
775
|
+
adjust: /* @__PURE__ */ new Map()
|
|
776
|
+
} : {
|
|
777
|
+
kind: "custom",
|
|
778
|
+
custom: {
|
|
779
|
+
pathWidth: 0,
|
|
780
|
+
pathHeight: 0,
|
|
781
|
+
commands: []
|
|
782
|
+
}
|
|
783
|
+
},
|
|
784
|
+
fill: lead?.shading ? {
|
|
785
|
+
kind: "solid",
|
|
786
|
+
colorHex: lead.shading.colorHex
|
|
787
|
+
} : { kind: "none" },
|
|
788
|
+
...edge && edge.style !== "none" ? { line: {
|
|
789
|
+
fill: "solid",
|
|
790
|
+
colorHex: edge.colorHex ?? "000000",
|
|
791
|
+
width: edge.width ?? pt(.75)
|
|
792
|
+
} } : {},
|
|
793
|
+
float: frameFloat(frame),
|
|
794
|
+
paragraphProperties: {},
|
|
795
|
+
text: {
|
|
796
|
+
content: paragraphs.map((paragraph) => ({
|
|
797
|
+
kind: "paragraph",
|
|
798
|
+
paragraph
|
|
799
|
+
})),
|
|
800
|
+
insetLeft: pt(0),
|
|
801
|
+
insetRight: pt(0),
|
|
802
|
+
insetTop: pt(0),
|
|
803
|
+
insetBottom: pt(0),
|
|
804
|
+
...vertical ? { vertical } : {},
|
|
805
|
+
...exact ? {} : { autoFit: true }
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
/** §17.3.1.11 → §20.4.2.3: the frame's anchor, in the terms floats are placed in. */
|
|
810
|
+
/**
|
|
811
|
+
* `wps:linkedTxbx` — carry each chain's overflow from one box to the next.
|
|
812
|
+
* The words all live in the chain's FIRST box; a box shows as many of its lines
|
|
813
|
+
* as its height holds and hands the rest on, so LinkedTextBoxes.docx reads down
|
|
814
|
+
* one column and continues at the top of the other. A box that ends a chain
|
|
815
|
+
* keeps whatever is left, overflowing as it would have anyway.
|
|
816
|
+
*
|
|
817
|
+
* The lines are broken at the first box's width, so a chain of boxes of
|
|
818
|
+
* different widths sets its later columns to the wrong measure; re-breaking
|
|
819
|
+
* needs the source paragraphs split at the carry point, which the laid-out
|
|
820
|
+
* lines no longer know.
|
|
821
|
+
*/
|
|
822
|
+
function flowLinkedTextBoxes(blocks) {
|
|
823
|
+
const chains = /* @__PURE__ */ new Map();
|
|
824
|
+
blocks.forEach((b, i) => {
|
|
825
|
+
if (b.kind !== "shape" || !b.textChain) return;
|
|
826
|
+
const at = chains.get(b.textChain.id) ?? [];
|
|
827
|
+
at.push(i);
|
|
828
|
+
chains.set(b.textChain.id, at);
|
|
829
|
+
});
|
|
830
|
+
for (const indices of chains.values()) {
|
|
831
|
+
if (indices.length < 2) continue;
|
|
832
|
+
indices.sort((a, b) => {
|
|
833
|
+
return (blocks[a].textChain?.seq ?? 0) - (blocks[b].textChain?.seq ?? 0);
|
|
834
|
+
});
|
|
835
|
+
let carried = [];
|
|
836
|
+
indices.forEach((idx, n) => {
|
|
837
|
+
const box = blocks[idx];
|
|
838
|
+
const all = n === 0 ? boxLines(box) : carried;
|
|
839
|
+
const last = n === indices.length - 1;
|
|
840
|
+
const room = box.heightPt - box.insetTopPt - box.insetBottomPt;
|
|
841
|
+
const kept = [];
|
|
842
|
+
let used = 0;
|
|
843
|
+
for (const l of all) {
|
|
844
|
+
const h = computeLineHeight(l.line, l.line.resolved) + l.gapPt;
|
|
845
|
+
if (!last && kept.length > 0 && used + h > room) break;
|
|
846
|
+
kept.push(l);
|
|
847
|
+
used += h;
|
|
848
|
+
}
|
|
849
|
+
carried = all.slice(kept.length);
|
|
850
|
+
blocks[idx] = rebuildBoxLines(box, kept);
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
function boxLines(box) {
|
|
855
|
+
return box.textLines.map((line, i) => ({
|
|
856
|
+
line,
|
|
857
|
+
gapPt: box.textLineGaps?.get(i) ?? 0,
|
|
858
|
+
...box.textCharts?.get(i) ? { chart: box.textCharts.get(i) } : {},
|
|
859
|
+
...box.textShapes?.get(i) ? { shape: box.textShapes.get(i) } : {},
|
|
860
|
+
...box.textTables?.get(i) ? { table: box.textTables.get(i) } : {}
|
|
861
|
+
}));
|
|
862
|
+
}
|
|
863
|
+
function rebuildBoxLines(box, lines) {
|
|
864
|
+
const charts = /* @__PURE__ */ new Map();
|
|
865
|
+
const shapes = /* @__PURE__ */ new Map();
|
|
866
|
+
const tables = /* @__PURE__ */ new Map();
|
|
867
|
+
const gaps = /* @__PURE__ */ new Map();
|
|
868
|
+
let height = 0;
|
|
869
|
+
lines.forEach((l, i) => {
|
|
870
|
+
if (l.chart) charts.set(i, l.chart);
|
|
871
|
+
if (l.shape) shapes.set(i, l.shape);
|
|
872
|
+
if (l.table) tables.set(i, l.table);
|
|
873
|
+
if (l.gapPt > 0) gaps.set(i, l.gapPt);
|
|
874
|
+
height += computeLineHeight(l.line, l.line.resolved) + l.gapPt;
|
|
875
|
+
});
|
|
876
|
+
const { textCharts: _c, textShapes: _s, textTables: _t, textLineGaps: _g, ...rest } = box;
|
|
877
|
+
return {
|
|
878
|
+
...rest,
|
|
879
|
+
textLines: lines.map((l) => l.line),
|
|
880
|
+
...charts.size > 0 ? { textCharts: charts } : {},
|
|
881
|
+
...shapes.size > 0 ? { textShapes: shapes } : {},
|
|
882
|
+
...tables.size > 0 ? { textTables: tables } : {},
|
|
883
|
+
...gaps.size > 0 ? { textLineGaps: gaps } : {},
|
|
884
|
+
textHeightPt: height
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* §17.3.1.11 — whether the frame is placed DOWN THE PAGE by itself, rather than
|
|
889
|
+
* where the flow has reached. `w:vAnchor="text"` (the default) measures `w:y`
|
|
890
|
+
* from the paragraph's own position, so such frames stack as paragraphs do —
|
|
891
|
+
* and a float takes no flow space, so lifting them out puts every one of them
|
|
892
|
+
* on the same line: tdf105035_framePrB.docx writes its title and its author
|
|
893
|
+
* line as text-anchored frames and both landed on the cursor, one over the
|
|
894
|
+
* other. Only a frame anchored to the page or the margin, or aligned by
|
|
895
|
+
* keyword, truly stands somewhere of its own.
|
|
896
|
+
*/
|
|
897
|
+
function framePositioned(frame) {
|
|
898
|
+
return frame.yAlign !== void 0 || frame.vAnchor !== void 0 && frame.vAnchor !== "text";
|
|
899
|
+
}
|
|
900
|
+
function frameFloat(frame) {
|
|
901
|
+
const wrap = frame.wrap === "none" ? "none" : frame.wrap === "notBeside" ? framePositioned(frame) ? "notBeside" : "topAndBottom" : "square";
|
|
902
|
+
const hRel = frame.hAnchor === "page" ? "page" : frame.hAnchor === "margin" ? "margin" : "column";
|
|
903
|
+
const vRel = frame.vAnchor === "page" ? "page" : frame.vAnchor === "margin" ? "margin" : "paragraph";
|
|
904
|
+
const align = frame.xAlign === "left" || frame.xAlign === "center" || frame.xAlign === "right" ? frame.xAlign : void 0;
|
|
905
|
+
const vAlign = frame.yAlign === "top" || frame.yAlign === "center" || frame.yAlign === "bottom" ? frame.yAlign : void 0;
|
|
906
|
+
return {
|
|
907
|
+
wrap,
|
|
908
|
+
posH: {
|
|
909
|
+
relativeFrom: hRel,
|
|
910
|
+
...align ? { align } : { offsetPt: frame.xPt ?? pt(0) }
|
|
911
|
+
},
|
|
912
|
+
posV: {
|
|
913
|
+
relativeFrom: vRel,
|
|
914
|
+
...vAlign ? { align: vAlign } : { offsetPt: frame.yPt ?? pt(0) }
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* A drawing's width when it states one as a share of the page or the margins.
|
|
920
|
+
*
|
|
921
|
+
* @param rel The relative size, if any.
|
|
922
|
+
* @param content The margin box's width.
|
|
923
|
+
* @param box The page box, when the caller knows it.
|
|
924
|
+
* @returns The width in points, or `undefined` when none is stated.
|
|
925
|
+
*/
|
|
926
|
+
function relativeWidth(rel, content, box) {
|
|
927
|
+
if (!rel?.widthPct) return void 0;
|
|
928
|
+
const from = rel.widthFrom;
|
|
929
|
+
return (box === void 0 || from === void 0 || from === "margin" ? content : from === "page" ? box.pageWidthPt : from === "leftMargin" ? box.marginLeftPt : box.marginRightPt) * rel.widthPct;
|
|
544
930
|
}
|
|
545
|
-
|
|
931
|
+
/** The height twin of {@link relativeWidth}; the margin box's height is the page's content height. */
|
|
932
|
+
function relativeHeight(rel, contentHeight, box) {
|
|
933
|
+
if (!rel?.heightPct) return void 0;
|
|
934
|
+
const from = rel.heightFrom;
|
|
935
|
+
const base = box === void 0 || from === void 0 || from === "margin" ? contentHeight : from === "page" ? box.pageHeightPt : from === "topMargin" ? box.marginTopPt : box.marginBottomPt;
|
|
936
|
+
return base === void 0 ? void 0 : base * rel.heightPct;
|
|
937
|
+
}
|
|
938
|
+
function laidOutBlocksFor(elements, options, fontResources, contentWidth, imageResources, box) {
|
|
939
|
+
const frames = frameGroups(elements);
|
|
940
|
+
return elements.map((el, idx) => {
|
|
941
|
+
const group = frames.starts.get(idx);
|
|
942
|
+
if (group) return layoutFrameBlock(group, options, fontResources, imageResources, contentWidth);
|
|
943
|
+
if (frames.continued.has(idx)) return emptyBlock(options);
|
|
944
|
+
return layoutBodyElement(el, options, fontResources, imageResources, contentWidth, void 0, box);
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
function layoutBodyElement(el, options, fontResources, imageResources, contentWidth, maxHeight, box) {
|
|
546
948
|
if (el.kind === "paragraph") return layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, contentWidth);
|
|
547
949
|
if (el.kind === "table") return layoutTableBlock(el.table, options, fontResources, imageResources, contentWidth);
|
|
548
|
-
if (el.kind === "image")
|
|
950
|
+
if (el.kind === "image") {
|
|
951
|
+
const meta = el.image.resource ? imageResources?.get(el.image.resource)?.metafile : void 0;
|
|
952
|
+
return meta ? layoutMetafileBlock(el.image, meta, options, fontResources, contentWidth, maxHeight, box) : layoutImageBlock(el.image, imageResources, contentWidth, maxHeight, box);
|
|
953
|
+
}
|
|
549
954
|
if (el.kind === "chart") return layoutChartBlock(el.chart, options, fontResources, contentWidth, maxHeight);
|
|
550
|
-
return layoutShapeBlock(el.shape, options, fontResources, imageResources, contentWidth, maxHeight);
|
|
955
|
+
return layoutShapeBlock(el.shape, options, fontResources, imageResources, contentWidth, maxHeight, box);
|
|
551
956
|
}
|
|
552
|
-
function layoutImageBlock(image, imageResources, contentWidth) {
|
|
553
|
-
let widthPt = image.width;
|
|
554
|
-
let heightPt = image.height;
|
|
555
|
-
if (widthPt > contentWidth) {
|
|
957
|
+
function layoutImageBlock(image, imageResources, contentWidth, maxHeight, box) {
|
|
958
|
+
let widthPt = relativeWidth(image.relativeSize, contentWidth, box) ?? image.width;
|
|
959
|
+
let heightPt = relativeHeight(image.relativeSize, maxHeight, box) ?? (image.relativeSize?.widthPct && image.width > 0 ? image.height * (widthPt / image.width) : image.height);
|
|
960
|
+
if (widthPt > contentWidth && !image.float) {
|
|
556
961
|
const scale = contentWidth / widthPt;
|
|
557
962
|
widthPt = contentWidth;
|
|
558
963
|
heightPt = heightPt * scale;
|
|
559
964
|
}
|
|
965
|
+
let drawInset;
|
|
966
|
+
const eff = image.effectExtent;
|
|
967
|
+
if (eff) {
|
|
968
|
+
const s = image.width > 0 ? widthPt / image.width : 1;
|
|
969
|
+
let box = {
|
|
970
|
+
dxPt: eff.leftPt * s,
|
|
971
|
+
dyTopPt: eff.topPt * s,
|
|
972
|
+
widthPt,
|
|
973
|
+
heightPt
|
|
974
|
+
};
|
|
975
|
+
let reservedW = widthPt + (eff.leftPt + eff.rightPt) * s;
|
|
976
|
+
let reservedH = heightPt + (eff.topPt + eff.bottomPt) * s;
|
|
977
|
+
if (reservedW > contentWidth) {
|
|
978
|
+
const k = contentWidth / reservedW;
|
|
979
|
+
reservedW = contentWidth;
|
|
980
|
+
reservedH *= k;
|
|
981
|
+
box = {
|
|
982
|
+
dxPt: box.dxPt * k,
|
|
983
|
+
dyTopPt: box.dyTopPt * k,
|
|
984
|
+
widthPt: box.widthPt * k,
|
|
985
|
+
heightPt: box.heightPt * k
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
widthPt = reservedW;
|
|
989
|
+
heightPt = reservedH;
|
|
990
|
+
drawInset = box;
|
|
991
|
+
}
|
|
560
992
|
const res = image.resource ? imageResources?.get(image.resource) : void 0;
|
|
561
993
|
const resolvedAlignment = image.paragraphProperties.alignment ?? "left";
|
|
562
994
|
return {
|
|
@@ -565,27 +997,34 @@ function layoutImageBlock(image, imageResources, contentWidth) {
|
|
|
565
997
|
heightPt,
|
|
566
998
|
resolvedAlignment,
|
|
567
999
|
resourceName: res?.resourceName ?? "",
|
|
1000
|
+
...image.outline ? { outline: image.outline } : {},
|
|
1001
|
+
...image.wash ? { wash: image.wash } : {},
|
|
1002
|
+
...image.crop ? { crop: image.crop } : {},
|
|
1003
|
+
...image.rotation60k ? { rotationDeg: image.rotation60k / 6e4 } : {},
|
|
1004
|
+
...image.flipH ? { flipH: true } : {},
|
|
1005
|
+
...image.flipV ? { flipV: true } : {},
|
|
1006
|
+
...drawInset ? { drawInset } : {},
|
|
568
1007
|
spacingBeforePt: image.paragraphProperties.spacingBefore ?? 0,
|
|
569
1008
|
spacingAfterPt: image.paragraphProperties.spacingAfter ?? 0,
|
|
570
1009
|
...image.altText ? { altText: image.altText } : {},
|
|
571
1010
|
...image.float ? { float: image.float } : {}
|
|
572
1011
|
};
|
|
573
1012
|
}
|
|
574
|
-
function layoutShapeBlock(shape, options, fontResources, imageResources, contentWidth, maxHeight) {
|
|
575
|
-
let widthPt = shape.width;
|
|
576
|
-
let heightPt = shape.height;
|
|
577
|
-
if (widthPt > contentWidth && widthPt > 0) {
|
|
1013
|
+
function layoutShapeBlock(shape, options, fontResources, imageResources, contentWidth, maxHeight, box, fitPass = 0) {
|
|
1014
|
+
let widthPt = relativeWidth(shape.relativeSize, contentWidth, box) ?? shape.width;
|
|
1015
|
+
let heightPt = relativeHeight(shape.relativeSize, maxHeight, box) ?? (shape.relativeSize?.widthPct && shape.width > 0 ? shape.height * (widthPt / shape.width) : shape.height);
|
|
1016
|
+
if (!shape.float && widthPt > contentWidth && widthPt > 0) {
|
|
578
1017
|
const scale = contentWidth / widthPt;
|
|
579
1018
|
widthPt = contentWidth;
|
|
580
1019
|
heightPt *= scale;
|
|
581
1020
|
}
|
|
582
|
-
if (maxHeight !== void 0 && heightPt > maxHeight && heightPt > 0) {
|
|
1021
|
+
if (!shape.float && maxHeight !== void 0 && heightPt > maxHeight && heightPt > 0) {
|
|
583
1022
|
const scale = maxHeight / heightPt;
|
|
584
1023
|
widthPt *= scale;
|
|
585
1024
|
heightPt = maxHeight;
|
|
586
1025
|
}
|
|
587
|
-
const paths = buildShapePaths(shape.geometry, widthPt, heightPt);
|
|
588
1026
|
const fillGradient = shape.fill.kind === "gradient" ? shape.fill.gradient : void 0;
|
|
1027
|
+
const fillImageResourceName = shape.fill.kind === "picture" && shape.fill.imageResource !== void 0 ? imageResources?.get(shape.fill.imageResource)?.resourceName : void 0;
|
|
589
1028
|
const fillColorHex = shape.fill.kind === "solid" ? shape.fill.colorHex : fillGradient ? gradientToSolid(fillGradient) : void 0;
|
|
590
1029
|
const stroke = buildStroke(shape.line);
|
|
591
1030
|
const t = shape.transform;
|
|
@@ -596,35 +1035,153 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
596
1035
|
const insetTopPt = text?.insetTop ?? 3.6;
|
|
597
1036
|
const insetBottomPt = text?.insetBottom ?? 3.6;
|
|
598
1037
|
const textLines = [];
|
|
1038
|
+
const textCharts = /* @__PURE__ */ new Map();
|
|
1039
|
+
const textShapes = /* @__PURE__ */ new Map();
|
|
1040
|
+
const textTables = /* @__PURE__ */ new Map();
|
|
1041
|
+
const textLineGaps = /* @__PURE__ */ new Map();
|
|
599
1042
|
let textHeightPt = 0;
|
|
1043
|
+
const vertical = text?.vertical;
|
|
600
1044
|
if (text && text.content.length > 0) {
|
|
601
|
-
const innerWidth = Math.max(1, widthPt - insetLeftPt - insetRightPt);
|
|
1045
|
+
const innerWidth = vertical ? Math.max(1, heightPt - insetTopPt - insetBottomPt) : Math.max(1, widthPt - insetLeftPt - insetRightPt);
|
|
602
1046
|
for (const el of text.content) {
|
|
603
|
-
if (el.kind
|
|
1047
|
+
if (el.kind === "image") {
|
|
1048
|
+
const res = el.image.resource ? imageResources?.get(el.image.resource) : void 0;
|
|
1049
|
+
const scaled = Math.min(1, innerWidth / Math.max(1, el.image.width));
|
|
1050
|
+
const line = {
|
|
1051
|
+
tokens: [{
|
|
1052
|
+
kind: "image",
|
|
1053
|
+
imageResourceName: res?.resourceName ?? "",
|
|
1054
|
+
widthPt: el.image.width * scaled,
|
|
1055
|
+
heightPt: el.image.height * scaled,
|
|
1056
|
+
isSpace: false,
|
|
1057
|
+
bidiLevel: 0
|
|
1058
|
+
}],
|
|
1059
|
+
contentWidthPt: el.image.width * scaled,
|
|
1060
|
+
maxFontSizePt: el.image.height * scaled,
|
|
1061
|
+
availableWidthPt: innerWidth,
|
|
1062
|
+
firstLine: true,
|
|
1063
|
+
resolved: resolveParagraphProperties(el.image.paragraphProperties, options.styles),
|
|
1064
|
+
isLastInParagraph: true
|
|
1065
|
+
};
|
|
1066
|
+
textLines.push(line);
|
|
1067
|
+
textHeightPt += computeLineHeight(line, line.resolved);
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
if (el.kind === "chart") {
|
|
1071
|
+
const laid = layoutChartBlock(el.chart, options, fontResources, innerWidth);
|
|
1072
|
+
const line = {
|
|
1073
|
+
tokens: [],
|
|
1074
|
+
contentWidthPt: laid.widthPt,
|
|
1075
|
+
maxFontSizePt: laid.heightPt,
|
|
1076
|
+
availableWidthPt: innerWidth,
|
|
1077
|
+
firstLine: true,
|
|
1078
|
+
resolved: resolveParagraphProperties(el.chart.paragraphProperties, options.styles),
|
|
1079
|
+
isLastInParagraph: true
|
|
1080
|
+
};
|
|
1081
|
+
textCharts.set(textLines.length, laid);
|
|
1082
|
+
textLines.push(line);
|
|
1083
|
+
textHeightPt += computeLineHeight(line, line.resolved);
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
if (el.kind === "shape") {
|
|
1087
|
+
const laid = layoutShapeBlock(el.shape, options, fontResources, imageResources, innerWidth);
|
|
1088
|
+
const line = {
|
|
1089
|
+
tokens: [],
|
|
1090
|
+
contentWidthPt: laid.widthPt,
|
|
1091
|
+
maxFontSizePt: laid.heightPt,
|
|
1092
|
+
availableWidthPt: innerWidth,
|
|
1093
|
+
firstLine: true,
|
|
1094
|
+
resolved: resolveParagraphProperties(el.shape.paragraphProperties, options.styles),
|
|
1095
|
+
isLastInParagraph: true
|
|
1096
|
+
};
|
|
1097
|
+
textShapes.set(textLines.length, laid);
|
|
1098
|
+
textLines.push(line);
|
|
1099
|
+
textHeightPt += computeLineHeight(line, line.resolved);
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
if (el.kind === "table") {
|
|
1103
|
+
const laid = layoutTableBlock(el.table, options, fontResources, imageResources, innerWidth);
|
|
1104
|
+
const line = {
|
|
1105
|
+
tokens: [],
|
|
1106
|
+
contentWidthPt: laid.totalWidthPt,
|
|
1107
|
+
maxFontSizePt: laid.heightPt,
|
|
1108
|
+
metricHeightPt: laid.heightPt,
|
|
1109
|
+
availableWidthPt: innerWidth,
|
|
1110
|
+
firstLine: true,
|
|
1111
|
+
resolved: resolveParagraphProperties({}, options.styles),
|
|
1112
|
+
isLastInParagraph: true
|
|
1113
|
+
};
|
|
1114
|
+
textTables.set(textLines.length, laid);
|
|
1115
|
+
textLines.push(line);
|
|
1116
|
+
textHeightPt += computeLineHeight(line, line.resolved);
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
604
1119
|
const blk = layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, innerWidth);
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
isLastInParagraph: true
|
|
613
|
-
}];
|
|
614
|
-
for (const line of lines) {
|
|
1120
|
+
const before = textLines.length > 0 ? blk.spacingBeforePt : 0;
|
|
1121
|
+
if (before > 0) {
|
|
1122
|
+
const last = textLines.length - 1;
|
|
1123
|
+
textLineGaps.set(last, (textLineGaps.get(last) ?? 0) + before);
|
|
1124
|
+
textHeightPt += before;
|
|
1125
|
+
}
|
|
1126
|
+
for (const line of blk.lines) {
|
|
615
1127
|
textLines.push(line);
|
|
616
1128
|
textHeightPt += computeLineHeight(line, blk.resolved);
|
|
617
1129
|
}
|
|
1130
|
+
if (blk.spacingAfterPt > 0) {
|
|
1131
|
+
const last = textLines.length - 1;
|
|
1132
|
+
textLineGaps.set(last, (textLineGaps.get(last) ?? 0) + blk.spacingAfterPt);
|
|
1133
|
+
}
|
|
618
1134
|
textHeightPt += blk.spacingAfterPt;
|
|
619
1135
|
}
|
|
620
1136
|
}
|
|
1137
|
+
if (text?.autoFit && !vertical && textLines.length > 0) heightPt = textHeightPt + insetTopPt + insetBottomPt;
|
|
1138
|
+
if (text?.fitToBox === true && textLines.length > 0 && fitPass < MAX_FIT_PASSES) {
|
|
1139
|
+
const innerW = Math.max(1, widthPt - insetLeftPt - insetRightPt);
|
|
1140
|
+
const innerH = Math.max(1, heightPt - insetTopPt - insetBottomPt);
|
|
1141
|
+
let widest = 0;
|
|
1142
|
+
let running = 0;
|
|
1143
|
+
let paragraphs = 0;
|
|
1144
|
+
for (const l of textLines) {
|
|
1145
|
+
running += l.contentWidthPt;
|
|
1146
|
+
if (l.isLastInParagraph) {
|
|
1147
|
+
widest = Math.max(widest, running);
|
|
1148
|
+
running = 0;
|
|
1149
|
+
paragraphs++;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
widest = Math.max(1, widest, running);
|
|
1153
|
+
const perLine = textHeightPt / Math.max(1, textLines.length);
|
|
1154
|
+
const naturalHeight = Math.max(1, perLine * Math.max(1, paragraphs || textLines.length));
|
|
1155
|
+
const k = Math.min(innerW / widest, innerH / naturalHeight);
|
|
1156
|
+
if (Number.isFinite(k) && k > .05 && k < 20 && Math.abs(k - 1) > .02) return layoutShapeBlock({
|
|
1157
|
+
...shape,
|
|
1158
|
+
text: scaleTextBody(text, k)
|
|
1159
|
+
}, options, fontResources, imageResources, contentWidth, maxHeight, box, fitPass + 1);
|
|
1160
|
+
}
|
|
1161
|
+
const paths = buildShapePaths(shape.geometry, widthPt, heightPt);
|
|
1162
|
+
const markerPaths = lineEndPaths(paths, shape.line, stroke?.widthPt ?? 0);
|
|
1163
|
+
const scale = shape.width > 0 ? widthPt / shape.width : 1;
|
|
1164
|
+
const children = (shape.children ?? []).map((child) => ({
|
|
1165
|
+
laid: layoutShapeBlock({
|
|
1166
|
+
...child.shape,
|
|
1167
|
+
width: pt(child.shape.width * scale),
|
|
1168
|
+
height: pt(child.shape.height * scale)
|
|
1169
|
+
}, options, fontResources, imageResources, Number.POSITIVE_INFINITY),
|
|
1170
|
+
xPt: child.xPt * scale,
|
|
1171
|
+
yPt: child.yPt * scale
|
|
1172
|
+
}));
|
|
621
1173
|
return {
|
|
622
1174
|
kind: "shape",
|
|
623
1175
|
widthPt,
|
|
624
1176
|
heightPt,
|
|
1177
|
+
...children.length > 0 ? { children } : {},
|
|
625
1178
|
paths,
|
|
1179
|
+
...markerPaths.length > 0 ? { markerPaths } : {},
|
|
1180
|
+
...fillImageResourceName ? { fillImageResourceName } : {},
|
|
1181
|
+
...fillImageResourceName && shape.fill.imageCrop ? { fillImageCrop: shape.fill.imageCrop } : {},
|
|
626
1182
|
...fillColorHex ? { fillColorHex } : {},
|
|
627
1183
|
...fillGradient ? { fillGradient } : {},
|
|
1184
|
+
...shape.fill.alpha !== void 0 && shape.fill.alpha < 1 ? { fillAlpha: shape.fill.alpha } : {},
|
|
628
1185
|
...stroke ? { stroke } : {},
|
|
629
1186
|
...shape.shadow ? { shadow: shape.shadow } : {},
|
|
630
1187
|
rotation60k: t?.rotation60k ?? 0,
|
|
@@ -634,16 +1191,160 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
634
1191
|
spacingBeforePt: pp.spacingBefore ?? 0,
|
|
635
1192
|
spacingAfterPt: pp.spacingAfter ?? 0,
|
|
636
1193
|
textLines,
|
|
1194
|
+
...textCharts.size > 0 ? { textCharts } : {},
|
|
1195
|
+
...textShapes.size > 0 ? { textShapes } : {},
|
|
1196
|
+
...textTables.size > 0 ? { textTables } : {},
|
|
1197
|
+
...textLineGaps.size > 0 ? { textLineGaps } : {},
|
|
637
1198
|
textHeightPt,
|
|
1199
|
+
...text?.chain ? { textChain: text.chain } : {},
|
|
638
1200
|
insetLeftPt,
|
|
639
1201
|
insetRightPt,
|
|
640
1202
|
insetTopPt,
|
|
641
1203
|
insetBottomPt,
|
|
642
1204
|
anchor: text?.anchor ?? "t",
|
|
1205
|
+
...vertical ? { vertical } : {},
|
|
643
1206
|
...shape.altText ? { altText: shape.altText } : {},
|
|
644
1207
|
...shape.float ? { float: shape.float } : {}
|
|
645
1208
|
};
|
|
646
1209
|
}
|
|
1210
|
+
/**
|
|
1211
|
+
* MS-EMF / MS-WMF — a metafile picture as the primitives a chart is made of,
|
|
1212
|
+
* scaled from its own logical box into the box the drawing reserves.
|
|
1213
|
+
*
|
|
1214
|
+
* A metafile IS a little drawing program, and what it draws is paths and words:
|
|
1215
|
+
* the same two things a chart's layout carries, placed in the same local y-up
|
|
1216
|
+
* frame. So one is turned into the other rather than growing a block kind, a
|
|
1217
|
+
* pagination arm and an emitter of its own.
|
|
1218
|
+
*/
|
|
1219
|
+
function layoutMetafileBlock(image, pic, options, fontResources, contentWidth, maxHeight, box) {
|
|
1220
|
+
const placed = layoutImageBlock(image, void 0, contentWidth, maxHeight, box);
|
|
1221
|
+
const widthPt = placed.widthPt;
|
|
1222
|
+
const heightPt = placed.heightPt;
|
|
1223
|
+
const layout = rotateDrawing(metafileDrawing(pic, widthPt, heightPt, options, fontResources), (image.rotation60k ?? 0) / 6e4, widthPt / 2, heightPt / 2);
|
|
1224
|
+
const pp = image.paragraphProperties;
|
|
1225
|
+
return {
|
|
1226
|
+
kind: "chart",
|
|
1227
|
+
widthPt,
|
|
1228
|
+
heightPt,
|
|
1229
|
+
layout,
|
|
1230
|
+
resolvedAlignment: pp.alignment ?? "left",
|
|
1231
|
+
spacingBeforePt: pp.spacingBefore ?? 0,
|
|
1232
|
+
spacingAfterPt: pp.spacingAfter ?? 0,
|
|
1233
|
+
figureRole: "Image",
|
|
1234
|
+
...image.altText ? { altText: image.altText } : {},
|
|
1235
|
+
...image.float ? { float: image.float } : {}
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
/** The same primitives, turned `deg` CLOCKWISE about a point of the local frame. */
|
|
1239
|
+
function rotateDrawing(layout, deg, cx, cy) {
|
|
1240
|
+
if (!deg) return layout;
|
|
1241
|
+
const a = -deg * Math.PI / 180;
|
|
1242
|
+
const cos = Math.cos(a);
|
|
1243
|
+
const sin = Math.sin(a);
|
|
1244
|
+
const at = (x, y) => ({
|
|
1245
|
+
x: cx + (x - cx) * cos - (y - cy) * sin,
|
|
1246
|
+
y: cy + (x - cx) * sin + (y - cy) * cos
|
|
1247
|
+
});
|
|
1248
|
+
return {
|
|
1249
|
+
shapes: layout.shapes.map((sh) => ({
|
|
1250
|
+
...sh,
|
|
1251
|
+
paths: sh.paths.map((path) => ({
|
|
1252
|
+
...path,
|
|
1253
|
+
segments: path.segments.map((seg) => {
|
|
1254
|
+
if (seg.op === "close") return seg;
|
|
1255
|
+
if (seg.op === "cubic") {
|
|
1256
|
+
const c1 = at(seg.x1, seg.y1);
|
|
1257
|
+
const c2 = at(seg.x2, seg.y2);
|
|
1258
|
+
const e = at(seg.x, seg.y);
|
|
1259
|
+
return {
|
|
1260
|
+
op: "cubic",
|
|
1261
|
+
x1: c1.x,
|
|
1262
|
+
y1: c1.y,
|
|
1263
|
+
x2: c2.x,
|
|
1264
|
+
y2: c2.y,
|
|
1265
|
+
x: e.x,
|
|
1266
|
+
y: e.y
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
const p = at(seg.x, seg.y);
|
|
1270
|
+
return {
|
|
1271
|
+
op: seg.op,
|
|
1272
|
+
x: p.x,
|
|
1273
|
+
y: p.y
|
|
1274
|
+
};
|
|
1275
|
+
})
|
|
1276
|
+
}))
|
|
1277
|
+
})),
|
|
1278
|
+
texts: layout.texts.map((t) => {
|
|
1279
|
+
const p = at(t.x, t.y);
|
|
1280
|
+
return {
|
|
1281
|
+
...t,
|
|
1282
|
+
x: p.x,
|
|
1283
|
+
y: p.y,
|
|
1284
|
+
rotationDeg: (t.rotationDeg ?? 0) - deg
|
|
1285
|
+
};
|
|
1286
|
+
})
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* A metafile's primitives, scaled from its own logical box into a `widthPt` ×
|
|
1291
|
+
* `heightPt` one and turned y-up about its bottom-left corner — the frame both
|
|
1292
|
+
* a chart block and an inline picture token are drawn in.
|
|
1293
|
+
*/
|
|
1294
|
+
function metafileDrawing(pic, widthPt, heightPt, options, fontResources) {
|
|
1295
|
+
const sx = widthPt / pic.width;
|
|
1296
|
+
const sy = heightPt / pic.height;
|
|
1297
|
+
const mapX = (x) => (x - pic.left) * sx;
|
|
1298
|
+
const mapY = (y) => heightPt - (y - pic.top) * sy;
|
|
1299
|
+
const shapes = [];
|
|
1300
|
+
const texts = [];
|
|
1301
|
+
for (const prim of pic.prims) {
|
|
1302
|
+
if (prim.kind === "path") {
|
|
1303
|
+
shapes.push({
|
|
1304
|
+
paths: prim.paths.map((path) => ({
|
|
1305
|
+
...path,
|
|
1306
|
+
segments: path.segments.map((seg) => seg.op === "close" ? seg : seg.op === "cubic" ? {
|
|
1307
|
+
op: "cubic",
|
|
1308
|
+
x1: mapX(seg.x1),
|
|
1309
|
+
y1: mapY(seg.y1),
|
|
1310
|
+
x2: mapX(seg.x2),
|
|
1311
|
+
y2: mapY(seg.y2),
|
|
1312
|
+
x: mapX(seg.x),
|
|
1313
|
+
y: mapY(seg.y)
|
|
1314
|
+
} : {
|
|
1315
|
+
op: seg.op,
|
|
1316
|
+
x: mapX(seg.x),
|
|
1317
|
+
y: mapY(seg.y)
|
|
1318
|
+
})
|
|
1319
|
+
})),
|
|
1320
|
+
...prim.fillColorHex !== void 0 ? { fillColorHex: prim.fillColorHex } : {},
|
|
1321
|
+
...prim.stroke ? { stroke: {
|
|
1322
|
+
...prim.stroke,
|
|
1323
|
+
widthPt: Math.max(.25, prim.stroke.widthPt * sx)
|
|
1324
|
+
} } : {}
|
|
1325
|
+
});
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
if (prim.kind !== "text") continue;
|
|
1329
|
+
const { variant } = options.registry.resolveByStyle(prim.bold === true, prim.italic === true);
|
|
1330
|
+
const font = fontResources.get(variant);
|
|
1331
|
+
if (!font) continue;
|
|
1332
|
+
const sizePt = Math.max(1, prim.sizeLu * sy);
|
|
1333
|
+
const line = makeChartLabelLine(prim.text, font, sizePt, prim.colorHex);
|
|
1334
|
+
const shift = prim.alignH === "center" ? -line.contentWidthPt / 2 : prim.alignH === "right" ? -line.contentWidthPt : 0;
|
|
1335
|
+
const baselineY = mapY(prim.y) - (prim.alignBaseline ? 0 : sizePt * .8);
|
|
1336
|
+
texts.push({
|
|
1337
|
+
line,
|
|
1338
|
+
x: mapX(prim.x) + shift,
|
|
1339
|
+
y: baselineY,
|
|
1340
|
+
...prim.escapement ? { rotationDeg: prim.escapement / 10 } : {}
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
return {
|
|
1344
|
+
shapes,
|
|
1345
|
+
texts
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
647
1348
|
function layoutChartBlock(block, options, fontResources, contentWidth, maxHeight) {
|
|
648
1349
|
let widthPt = block.width;
|
|
649
1350
|
let heightPt = block.height;
|
|
@@ -693,6 +1394,7 @@ function buildChartLayout(chart, widthPt, heightPt, font) {
|
|
|
693
1394
|
};
|
|
694
1395
|
const shapes = [];
|
|
695
1396
|
if (scene.background) shapes.push(rectPrim(scene.background));
|
|
1397
|
+
if (scene.plotBackground) shapes.push(rectPrim(scene.plotBackground));
|
|
696
1398
|
for (const g of scene.gridlines ?? []) shapes.push(polylinePrim(g));
|
|
697
1399
|
for (const pg of scene.polygons ?? []) shapes.push(polygonPrim(pg));
|
|
698
1400
|
for (const r of scene.rects) shapes.push(rectPrim(r));
|
|
@@ -818,14 +1520,20 @@ function circlePath(cx, cy, r) {
|
|
|
818
1520
|
const k = .5522847498307936 * r;
|
|
819
1521
|
return new PathBuilder().moveTo(cx + r, cy).cubicTo(cx + r, cy + k, cx + k, cy + r, cx, cy + r).cubicTo(cx - k, cy + r, cx - r, cy + k, cx - r, cy).cubicTo(cx - r, cy - k, cx - k, cy - r, cx, cy - r).cubicTo(cx + k, cy - r, cx + r, cy - k, cx + r, cy).close().build();
|
|
820
1522
|
}
|
|
1523
|
+
function strokeOfRect(r) {
|
|
1524
|
+
if (!r.strokeHex) return {};
|
|
1525
|
+
const stroke = buildStroke({
|
|
1526
|
+
colorHex: r.strokeHex,
|
|
1527
|
+
width: pt(r.strokeWidthPt ?? 1),
|
|
1528
|
+
...r.strokeDash ? { dash: r.strokeDash } : {}
|
|
1529
|
+
});
|
|
1530
|
+
return stroke ? { stroke } : {};
|
|
1531
|
+
}
|
|
821
1532
|
function rectPrim(r) {
|
|
822
1533
|
return {
|
|
823
1534
|
paths: [rectAtPath(r.x, r.y, r.w, r.h)],
|
|
824
1535
|
...r.fillHex ? { fillColorHex: r.fillHex } : {},
|
|
825
|
-
...r
|
|
826
|
-
colorHex: r.strokeHex,
|
|
827
|
-
widthPt: r.strokeWidthPt ?? 1
|
|
828
|
-
} } : {}
|
|
1536
|
+
...strokeOfRect(r)
|
|
829
1537
|
};
|
|
830
1538
|
}
|
|
831
1539
|
function polylinePrim(p) {
|
|
@@ -915,10 +1623,16 @@ function collectImageResources(body, headersFooters, options) {
|
|
|
915
1623
|
for (const run of el.paragraph.runs) if (run.inlineImage?.resource) seen.add(run.inlineImage.resource);
|
|
916
1624
|
} else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
|
|
917
1625
|
else if (el.kind === "shape") {
|
|
918
|
-
|
|
1626
|
+
const shapeImages = (sh) => {
|
|
1627
|
+
if (sh.fill.imageResource) seen.add(sh.fill.imageResource);
|
|
1628
|
+
if (sh.text) visit(sh.text.content);
|
|
1629
|
+
for (const child of sh.children ?? []) shapeImages(child.shape);
|
|
1630
|
+
};
|
|
1631
|
+
shapeImages(el.shape);
|
|
919
1632
|
}
|
|
920
1633
|
};
|
|
921
1634
|
visit(body);
|
|
1635
|
+
if (options.pageBackgroundFill?.imageResource !== void 0) seen.add(options.pageBackgroundFill.imageResource);
|
|
922
1636
|
for (const hf of headersFooters.values()) visit(hf);
|
|
923
1637
|
for (const note of options.footnotes?.values() ?? []) visit(note);
|
|
924
1638
|
for (const note of options.endnotes?.values() ?? []) visit(note);
|
|
@@ -928,6 +1642,15 @@ function collectImageResources(body, headersFooters, options) {
|
|
|
928
1642
|
for (const resourceId of seen) {
|
|
929
1643
|
const bytes = options.resources.get(resourceId);
|
|
930
1644
|
if (!bytes) continue;
|
|
1645
|
+
if (isEmf(bytes) || isWmf(bytes)) {
|
|
1646
|
+
try {
|
|
1647
|
+
out.set(resourceId, {
|
|
1648
|
+
resourceName: "",
|
|
1649
|
+
metafile: isEmf(bytes) ? readEmf(bytes) : readWmf(bytes)
|
|
1650
|
+
});
|
|
1651
|
+
} catch {}
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
931
1654
|
let prepared;
|
|
932
1655
|
try {
|
|
933
1656
|
prepared = prepareImage(bytes, { flattenAlpha });
|
|
@@ -942,12 +1665,296 @@ function collectImageResources(body, headersFooters, options) {
|
|
|
942
1665
|
}
|
|
943
1666
|
return out;
|
|
944
1667
|
}
|
|
945
|
-
function
|
|
1668
|
+
function chartPageItems(laid, x, bottomYUp, pageHeight, structId) {
|
|
1669
|
+
const fig = structId !== void 0 ? { structId } : {};
|
|
1670
|
+
const out = [];
|
|
1671
|
+
for (const sh of laid.layout.shapes) out.push({
|
|
1672
|
+
type: "shape",
|
|
1673
|
+
shape: {
|
|
1674
|
+
paths: sh.paths,
|
|
1675
|
+
...sh.fillColorHex ? { fillColorHex: sh.fillColorHex } : {},
|
|
1676
|
+
...sh.stroke ? { stroke: sh.stroke } : {},
|
|
1677
|
+
transform: flipTransform([
|
|
1678
|
+
1,
|
|
1679
|
+
0,
|
|
1680
|
+
0,
|
|
1681
|
+
1,
|
|
1682
|
+
x,
|
|
1683
|
+
bottomYUp
|
|
1684
|
+
], pageHeight)
|
|
1685
|
+
},
|
|
1686
|
+
...fig
|
|
1687
|
+
});
|
|
1688
|
+
for (const t of laid.layout.texts) out.push({
|
|
1689
|
+
type: "line",
|
|
1690
|
+
line: t.line,
|
|
1691
|
+
originX: pt(x + t.x),
|
|
1692
|
+
baselineY: pt(pageHeight - (bottomYUp + t.y)),
|
|
1693
|
+
...t.rotationDeg ? { rotationDeg: t.rotationDeg } : {},
|
|
1694
|
+
...fig
|
|
1695
|
+
});
|
|
1696
|
+
return out;
|
|
1697
|
+
}
|
|
1698
|
+
function emitShapeText(sh, x, bottomYUp, sink, pageHeight, figId) {
|
|
1699
|
+
if (sh.textLines.length === 0) return;
|
|
1700
|
+
if (sh.vertical) {
|
|
1701
|
+
const up = sh.vertical === "vert270";
|
|
1702
|
+
const innerHeight = Math.max(1, sh.heightPt - sh.insetTopPt - sh.insetBottomPt);
|
|
1703
|
+
let across = sh.anchor === "b" ? sh.widthPt - sh.insetRightPt - sh.textHeightPt : sh.anchor === "ctr" ? (sh.widthPt - sh.textHeightPt) / 2 : sh.insetLeftPt;
|
|
1704
|
+
for (const line of sh.textLines) {
|
|
1705
|
+
const h = computeLineHeight(line, line.resolved);
|
|
1706
|
+
const lineOffset = alignmentOffset(line.resolved.alignment, line.contentWidthPt, innerHeight);
|
|
1707
|
+
const off = lineBaselineOffset(line, line.resolved);
|
|
1708
|
+
const baselineAcross = up ? across + h - off : across + off;
|
|
1709
|
+
sink.push({
|
|
1710
|
+
type: "line",
|
|
1711
|
+
line,
|
|
1712
|
+
originX: pt(up ? x + baselineAcross : x + sh.widthPt - baselineAcross),
|
|
1713
|
+
baselineY: pt(pageHeight - (up ? bottomYUp + sh.insetBottomPt + lineOffset : bottomYUp + sh.heightPt - sh.insetTopPt - lineOffset)),
|
|
1714
|
+
rotationDeg: up ? 90 : -90,
|
|
1715
|
+
...figId !== void 0 ? { structId: figId } : {}
|
|
1716
|
+
});
|
|
1717
|
+
across += h;
|
|
1718
|
+
}
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
const shapeTop = bottomYUp + sh.heightPt;
|
|
1722
|
+
const innerWidth = Math.max(1, sh.widthPt - sh.insetLeftPt - sh.insetRightPt);
|
|
1723
|
+
let textY;
|
|
1724
|
+
if (sh.anchor === "b") textY = bottomYUp + sh.insetBottomPt + sh.textHeightPt;
|
|
1725
|
+
else if (sh.anchor === "ctr") textY = bottomYUp + (sh.heightPt + sh.textHeightPt) / 2;
|
|
1726
|
+
else textY = shapeTop - sh.insetTopPt;
|
|
1727
|
+
sh.textLines.forEach((line, i) => {
|
|
1728
|
+
textY -= computeLineHeight(line, line.resolved);
|
|
1729
|
+
const lineOffset = alignmentOffset(line.resolved.alignment, line.contentWidthPt, innerWidth);
|
|
1730
|
+
const nested = sh.textCharts?.get(i);
|
|
1731
|
+
if (nested) {
|
|
1732
|
+
sink.push(...chartPageItems(nested, x + sh.insetLeftPt + lineOffset, textY, pageHeight, figId));
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
const drawing = sh.textShapes?.get(i);
|
|
1736
|
+
if (drawing) {
|
|
1737
|
+
emitShapeItems(drawing, x + sh.insetLeftPt + lineOffset, textY, sink, pageHeight, figId);
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
const table = sh.textTables?.get(i);
|
|
1741
|
+
if (table) {
|
|
1742
|
+
let rowY = textY + table.heightPt;
|
|
1743
|
+
for (const row of table.rows) {
|
|
1744
|
+
emitRowChunk(sink, row, x + sh.insetLeftPt + lineOffset + table.xOffsetPt, rowY, pageHeight, table.colCount);
|
|
1745
|
+
rowY -= row.heightPt;
|
|
1746
|
+
}
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
sink.push({
|
|
1750
|
+
type: "line",
|
|
1751
|
+
line,
|
|
1752
|
+
originX: pt(x + sh.insetLeftPt + lineOffset),
|
|
1753
|
+
baselineY: pt(pageHeight - (textY + lineBaselineOffset(line, line.resolved))),
|
|
1754
|
+
...figId !== void 0 ? { structId: figId } : {}
|
|
1755
|
+
});
|
|
1756
|
+
textY -= sh.textLineGaps?.get(i) ?? 0;
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
/** The page a table is being drawn onto, as a cell's floats need to see it. */
|
|
1760
|
+
function cellAnchorCtx(asm) {
|
|
1761
|
+
return {
|
|
1762
|
+
sink: (behind, zOrder) => PageAssembler.zSink(behind ? asm.floatsBehind : asm.floatsFront, zOrder),
|
|
1763
|
+
frame: asm.floatFrame(),
|
|
1764
|
+
exclude: (f, x, topYUp, widthPt, heightPt) => {
|
|
1765
|
+
asm.excludeFloat(f, x, topYUp, widthPt, heightPt);
|
|
1766
|
+
},
|
|
1767
|
+
pageHeight: asm.ctx.pageHeight
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
function emitCellFloat(cf, ctx, frame) {
|
|
1771
|
+
const float = cf.block.float;
|
|
1772
|
+
if (!float) return;
|
|
1773
|
+
const x = floatXIn(frame, float, cf.block.widthPt);
|
|
1774
|
+
const topYUp = floatTopYUpIn(frame, float, cf.block.heightPt);
|
|
1775
|
+
const sink = ctx.sink(float.behind === true, float.zOrder);
|
|
1776
|
+
if (cf.block.kind === "shape") emitShapeItems(cf.block, x, topYUp - cf.block.heightPt, sink, ctx.pageHeight, void 0);
|
|
1777
|
+
else {
|
|
1778
|
+
const ins = cf.block.drawInset;
|
|
1779
|
+
sink.push({
|
|
1780
|
+
type: "image",
|
|
1781
|
+
x: pt(x + (ins?.dxPt ?? 0)),
|
|
1782
|
+
y: pt(ctx.pageHeight - (topYUp - (ins?.dyTopPt ?? 0))),
|
|
1783
|
+
width: pt(ins?.widthPt ?? cf.block.widthPt),
|
|
1784
|
+
height: pt(ins?.heightPt ?? cf.block.heightPt),
|
|
1785
|
+
imageResourceName: cf.block.resourceName,
|
|
1786
|
+
...cf.block.crop ? { crop: cf.block.crop } : {},
|
|
1787
|
+
...cf.block.wash ? { wash: cf.block.wash } : {},
|
|
1788
|
+
...cf.block.rotationDeg ? { rotationDeg: cf.block.rotationDeg } : {},
|
|
1789
|
+
...cf.block.flipH ? { flipH: true } : {},
|
|
1790
|
+
...cf.block.flipV ? { flipV: true } : {}
|
|
1791
|
+
});
|
|
1792
|
+
if (cf.block.outline) sink.push(...pictureOutlineItems(cf.block.outline, x + (ins?.dxPt ?? 0), ctx.pageHeight - (topYUp - (ins?.dyTopPt ?? 0)), ins?.widthPt ?? cf.block.widthPt, ins?.heightPt ?? cf.block.heightPt, void 0));
|
|
1793
|
+
}
|
|
1794
|
+
if (float.wrap !== "none") ctx.exclude(float, x, topYUp, cf.block.widthPt, cf.block.heightPt);
|
|
1795
|
+
}
|
|
1796
|
+
function emitShapeItems(sh, x, bottomYUp, sink, pageHeight, figId) {
|
|
1797
|
+
if (sh.fillImageResourceName) sink.push({
|
|
1798
|
+
type: "image",
|
|
1799
|
+
x: pt(x),
|
|
1800
|
+
y: pt(pageHeight - (bottomYUp + sh.heightPt)),
|
|
1801
|
+
width: pt(sh.widthPt),
|
|
1802
|
+
height: pt(sh.heightPt),
|
|
1803
|
+
imageResourceName: sh.fillImageResourceName,
|
|
1804
|
+
...sh.fillImageCrop ? { crop: sh.fillImageCrop } : {},
|
|
1805
|
+
clip: {
|
|
1806
|
+
paths: sh.paths,
|
|
1807
|
+
transform: flipTransform(buildShapeTransform(x, bottomYUp, sh.widthPt, sh.heightPt, sh.rotation60k, sh.flipH, sh.flipV), pageHeight)
|
|
1808
|
+
},
|
|
1809
|
+
...figId !== void 0 ? { structId: figId } : {}
|
|
1810
|
+
});
|
|
1811
|
+
if (sh.markerPaths && sh.stroke) sink.push({
|
|
1812
|
+
type: "shape",
|
|
1813
|
+
shape: {
|
|
1814
|
+
paths: sh.markerPaths,
|
|
1815
|
+
fillColorHex: sh.stroke.colorHex,
|
|
1816
|
+
transform: flipTransform(buildShapeTransform(x, bottomYUp, sh.widthPt, sh.heightPt, sh.rotation60k, sh.flipH, sh.flipV), pageHeight)
|
|
1817
|
+
},
|
|
1818
|
+
...figId !== void 0 ? { structId: figId } : {}
|
|
1819
|
+
});
|
|
1820
|
+
if (sh.paths.some((path) => path.segments.length > 0)) sink.push({
|
|
1821
|
+
type: "shape",
|
|
1822
|
+
shape: {
|
|
1823
|
+
paths: sh.paths,
|
|
1824
|
+
...sh.fillColorHex ? { fillColorHex: sh.fillColorHex } : {},
|
|
1825
|
+
...sh.fillGradient ? { fillGradient: sh.fillGradient } : {},
|
|
1826
|
+
...sh.fillAlpha !== void 0 ? { fillAlpha: sh.fillAlpha } : {},
|
|
1827
|
+
...sh.stroke ? { stroke: sh.stroke } : {},
|
|
1828
|
+
...sh.shadow ? { shadow: sh.shadow } : {},
|
|
1829
|
+
transform: flipTransform(buildShapeTransform(x, bottomYUp, sh.widthPt, sh.heightPt, sh.rotation60k, sh.flipH, sh.flipV), pageHeight)
|
|
1830
|
+
},
|
|
1831
|
+
...figId !== void 0 ? { structId: figId } : {}
|
|
1832
|
+
});
|
|
1833
|
+
for (const child of sh.children ?? []) emitShapeItems(child.laid, x + child.xPt, bottomYUp + sh.heightPt - child.yPt - child.laid.heightPt, sink, pageHeight, figId);
|
|
1834
|
+
emitShapeText(sh, x, bottomYUp, sink, pageHeight, figId);
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* §20.1.2.2.24 — the four rules of a picture's own frame, around the box the
|
|
1838
|
+
* picture is drawn in. Borders paint after images and before shapes, so the
|
|
1839
|
+
* frame lands on top of the picture the way Word draws it.
|
|
1840
|
+
*
|
|
1841
|
+
* @param outline The picture's frame.
|
|
1842
|
+
* @param x Left edge of the picture box.
|
|
1843
|
+
* @param topY Distance from the page TOP down to the box's top edge.
|
|
1844
|
+
* @param widthPt Box width.
|
|
1845
|
+
* @param heightPt Box height.
|
|
1846
|
+
* @param structId Tagged-PDF structure node the picture belongs to.
|
|
1847
|
+
* @returns One {@link BorderItem} per side.
|
|
1848
|
+
*/
|
|
1849
|
+
function pictureOutlineItems(outline, x, topY, widthPt, heightPt, structId) {
|
|
1850
|
+
return [
|
|
1851
|
+
"top",
|
|
1852
|
+
"bottom",
|
|
1853
|
+
"left",
|
|
1854
|
+
"right"
|
|
1855
|
+
].map((side) => ({
|
|
1856
|
+
type: "border",
|
|
1857
|
+
side,
|
|
1858
|
+
x: pt(x),
|
|
1859
|
+
y: pt(topY),
|
|
1860
|
+
width: pt(widthPt),
|
|
1861
|
+
height: pt(heightPt),
|
|
1862
|
+
borderSizePt: outline.widthPt,
|
|
1863
|
+
borderColorHex: outline.colorHex,
|
|
1864
|
+
...structId !== void 0 ? { structId } : {}
|
|
1865
|
+
}));
|
|
1866
|
+
}
|
|
1867
|
+
function bandAnchor(float, widthPt, contentWidth, bandLeftPt = 0) {
|
|
1868
|
+
if (!float) return void 0;
|
|
1869
|
+
const align = float.posH?.align;
|
|
1870
|
+
if (align === "right") return contentWidth - widthPt;
|
|
1871
|
+
if (align === "center") return (contentWidth - widthPt) / 2;
|
|
1872
|
+
if (align === "left") return 0;
|
|
1873
|
+
const offset = float.posH?.offsetPt ?? 0;
|
|
1874
|
+
return float.posH?.relativeFrom === "page" ? offset - bandLeftPt : offset;
|
|
1875
|
+
}
|
|
1876
|
+
/**
|
|
1877
|
+
* §20.4.2.3 — how far DOWN the page a band's anchored drawing starts, in the
|
|
1878
|
+
* band emitter's y-UP frame. A `page`-relative anchor is measured from the
|
|
1879
|
+
* paper's own top edge, whatever the band's cursor has reached: n773061.docx
|
|
1880
|
+
* hangs its header text box 92pt down the page and, measured from the header's
|
|
1881
|
+
* first paragraph, it landed 60pt lower than Word and LibreOffice put it.
|
|
1882
|
+
*
|
|
1883
|
+
* @param float The anchor.
|
|
1884
|
+
* @param cursorY The band's cursor (y-up), where a paragraph-relative
|
|
1885
|
+
* anchor is measured from.
|
|
1886
|
+
* @param pageHeight The page height.
|
|
1887
|
+
* @returns The drawing's top edge, y-up.
|
|
1888
|
+
*/
|
|
1889
|
+
function bandAnchorTop(float, cursorY, pageHeight, heightPt = 0, margins) {
|
|
1890
|
+
const align = float.posV?.align;
|
|
1891
|
+
if (align !== void 0) {
|
|
1892
|
+
const page = float.posV?.relativeFrom === "page" || !margins;
|
|
1893
|
+
const topYUp = page ? pageHeight : pageHeight - margins.top;
|
|
1894
|
+
const bottomYUp = page ? 0 : margins.bottom;
|
|
1895
|
+
if (align === "top") return topYUp;
|
|
1896
|
+
if (align === "bottom") return bottomYUp + heightPt;
|
|
1897
|
+
return bottomYUp + (topYUp - bottomYUp + heightPt) / 2;
|
|
1898
|
+
}
|
|
1899
|
+
const offset = float.posV?.offsetPt ?? 0;
|
|
1900
|
+
return float.posV?.relativeFrom === "page" ? pageHeight - offset : cursorY - offset;
|
|
1901
|
+
}
|
|
1902
|
+
function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth, structId, bandMargins) {
|
|
946
1903
|
const out = [];
|
|
947
1904
|
let cursorY = startY;
|
|
948
|
-
|
|
949
|
-
|
|
1905
|
+
const isBehind = (b) => "float" in b && b.float?.behind === true;
|
|
1906
|
+
const ordered = blocks.some(isBehind) ? [...blocks.filter(isBehind), ...blocks.filter((b) => !isBehind(b))] : blocks;
|
|
1907
|
+
for (const block of ordered) {
|
|
1908
|
+
if (block.kind === "table") {
|
|
1909
|
+
const tableX = startX + block.xOffsetPt;
|
|
1910
|
+
for (const row of block.rows) {
|
|
1911
|
+
emitRowChunk(out, row, tableX, cursorY, pageHeight, block.colCount);
|
|
1912
|
+
cursorY -= row.heightPt;
|
|
1913
|
+
}
|
|
1914
|
+
continue;
|
|
1915
|
+
}
|
|
1916
|
+
if (block.kind === "chart") {
|
|
1917
|
+
cursorY -= block.spacingBeforePt + block.heightPt;
|
|
1918
|
+
const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
|
|
1919
|
+
out.push(...chartPageItems(block, startX + offset, cursorY, pageHeight, structId));
|
|
1920
|
+
cursorY -= block.spacingAfterPt;
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1923
|
+
if (block.kind === "image") {
|
|
1924
|
+
cursorY -= block.spacingBeforePt;
|
|
1925
|
+
const anchored = bandAnchor(block.float, block.widthPt, contentWidth, startX);
|
|
1926
|
+
const offset = anchored ?? alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
|
|
1927
|
+
const top = anchored === void 0 || !block.float ? cursorY : bandAnchorTop(block.float, cursorY, pageHeight, block.heightPt, bandMargins);
|
|
1928
|
+
out.push({
|
|
1929
|
+
type: "image",
|
|
1930
|
+
x: pt(startX + offset),
|
|
1931
|
+
y: pt(pageHeight - top),
|
|
1932
|
+
width: pt(block.widthPt),
|
|
1933
|
+
height: pt(block.heightPt),
|
|
1934
|
+
imageResourceName: block.resourceName,
|
|
1935
|
+
...block.crop ? { crop: block.crop } : {},
|
|
1936
|
+
...block.wash ? { wash: block.wash } : {},
|
|
1937
|
+
...block.rotationDeg ? { rotationDeg: block.rotationDeg } : {},
|
|
1938
|
+
...block.flipH ? { flipH: true } : {},
|
|
1939
|
+
...block.flipV ? { flipV: true } : {}
|
|
1940
|
+
});
|
|
1941
|
+
if (block.outline) out.push(...pictureOutlineItems(block.outline, startX + offset, pageHeight - top, block.widthPt, block.heightPt, structId));
|
|
1942
|
+
if (anchored === void 0) cursorY -= block.heightPt;
|
|
1943
|
+
cursorY -= block.spacingAfterPt;
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
if (block.kind === "shape") {
|
|
1947
|
+
cursorY -= block.spacingBeforePt;
|
|
1948
|
+
const anchored = bandAnchor(block.float, block.widthPt, contentWidth, startX);
|
|
1949
|
+
const offset = anchored ?? alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
|
|
1950
|
+
const top = anchored === void 0 || !block.float ? cursorY : bandAnchorTop(block.float, cursorY, pageHeight, block.heightPt, bandMargins);
|
|
1951
|
+
emitShapeItems(block, startX + offset, top - block.heightPt, out, pageHeight, structId);
|
|
1952
|
+
if (anchored === void 0) cursorY -= block.heightPt;
|
|
1953
|
+
cursorY -= block.spacingAfterPt;
|
|
1954
|
+
continue;
|
|
1955
|
+
}
|
|
950
1956
|
cursorY -= block.spacingBeforePt;
|
|
1957
|
+
const bandTopY = cursorY;
|
|
951
1958
|
for (const line of block.lines) {
|
|
952
1959
|
const h = computeLineHeight(line, block.resolved);
|
|
953
1960
|
cursorY -= h;
|
|
@@ -957,14 +1964,60 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, structId) {
|
|
|
957
1964
|
type: "line",
|
|
958
1965
|
line,
|
|
959
1966
|
originX: pt(startX + indentLeft + offset),
|
|
960
|
-
baselineY: pt(pageHeight - (cursorY +
|
|
1967
|
+
baselineY: pt(pageHeight - (cursorY + lineBaselineOffset(line, block.resolved))),
|
|
961
1968
|
...structId !== void 0 ? { structId } : {}
|
|
962
1969
|
});
|
|
963
1970
|
}
|
|
1971
|
+
out.push(...paragraphDecoration(block.resolved, startX + block.resolved.indentLeft, startX + contentWidth - block.resolved.indentRight, bandTopY, cursorY, pageHeight));
|
|
964
1972
|
cursorY -= block.spacingAfterPt;
|
|
965
1973
|
}
|
|
966
1974
|
return out;
|
|
967
1975
|
}
|
|
1976
|
+
/**
|
|
1977
|
+
* §17.3.1.31 `w:shd` and §17.3.1.24 `w:pBdr` — the band behind a paragraph and
|
|
1978
|
+
* the rules around it, for a paragraph occupying `topY`…`bottomY` (both in the
|
|
1979
|
+
* y-up page frame) between `x0` and `x1`.
|
|
1980
|
+
*
|
|
1981
|
+
* @returns The fill and border items, in paint order.
|
|
1982
|
+
*/
|
|
1983
|
+
function paragraphDecoration(resolved, x0, x1, topY, bottomY, pageHeight) {
|
|
1984
|
+
const out = [];
|
|
1985
|
+
const width = Math.max(0, x1 - x0);
|
|
1986
|
+
if (resolved.shading) out.push({
|
|
1987
|
+
type: "fill",
|
|
1988
|
+
x: pt(x0),
|
|
1989
|
+
y: pt(pageHeight - topY),
|
|
1990
|
+
width: pt(width),
|
|
1991
|
+
height: pt(Math.max(0, topY - bottomY)),
|
|
1992
|
+
fillColorHex: resolved.shading.colorHex
|
|
1993
|
+
});
|
|
1994
|
+
const borders = resolved.borders;
|
|
1995
|
+
if (!borders) return out;
|
|
1996
|
+
for (const side of [
|
|
1997
|
+
"top",
|
|
1998
|
+
"bottom",
|
|
1999
|
+
"left",
|
|
2000
|
+
"right"
|
|
2001
|
+
]) {
|
|
2002
|
+
const edge = borders[side];
|
|
2003
|
+
if (!edge || edge.style === "none") continue;
|
|
2004
|
+
const gap = edge.spacePt ?? 0;
|
|
2005
|
+
const top = topY + gap;
|
|
2006
|
+
const bottom = bottomY - gap;
|
|
2007
|
+
out.push({
|
|
2008
|
+
type: "border",
|
|
2009
|
+
side,
|
|
2010
|
+
x: pt(x0),
|
|
2011
|
+
y: pt(pageHeight - top),
|
|
2012
|
+
width: pt(width),
|
|
2013
|
+
height: pt(Math.max(0, top - bottom)),
|
|
2014
|
+
borderSizePt: edge.width ?? DEFAULT_BORDER_SIZE_EIGHTH * EIGHTH_PT,
|
|
2015
|
+
borderColorHex: edge.colorHex ?? "000000",
|
|
2016
|
+
...edge.style !== "single" ? { borderStyle: edge.style } : {}
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
return out;
|
|
2020
|
+
}
|
|
968
2021
|
function runFontKeyAndParsed(options, ascii, bold, italic) {
|
|
969
2022
|
if (ascii && options.embeddedFonts) {
|
|
970
2023
|
const name = ascii.trim().toLowerCase();
|
|
@@ -1000,7 +2053,7 @@ function runFontKeyAndParsed(options, ascii, bold, italic) {
|
|
|
1000
2053
|
function lookupFont(resources, fontKey) {
|
|
1001
2054
|
return resources.get(fontKey) ?? resources.values().next().value;
|
|
1002
2055
|
}
|
|
1003
|
-
function collectFontResources(body, headersFooters, options) {
|
|
2056
|
+
function collectFontResources(body, headersFooters, options, imageResources) {
|
|
1004
2057
|
const used = /* @__PURE__ */ new Map();
|
|
1005
2058
|
const addRun = (run, para) => {
|
|
1006
2059
|
const resolved = resolveRunProperties(run.properties, para.properties, options.styles);
|
|
@@ -1015,6 +2068,10 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1015
2068
|
}
|
|
1016
2069
|
const shaped = shapeText(run.text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, parsed.kerning, parsed.joiningForms);
|
|
1017
2070
|
for (const g of shaped.gids) bucket.gids.add(g);
|
|
2071
|
+
if (run.text.includes(" ")) for (const stop of resolveParagraphProperties(para.properties, options.styles).tabs) {
|
|
2072
|
+
const ch = stop.leader === void 0 ? void 0 : TAB_LEADER_CHARS.get(stop.leader);
|
|
2073
|
+
if (ch !== void 0) bucket.gids.add(parsed.glyphForCodepoint(ch.codePointAt(0)));
|
|
2074
|
+
}
|
|
1018
2075
|
};
|
|
1019
2076
|
const addMath = (node) => {
|
|
1020
2077
|
for (const seg of mathGlyphSegments(node)) {
|
|
@@ -1031,30 +2088,48 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1031
2088
|
for (const ch of seg.text) bucket.gids.add(parsed.glyphForCodepoint(ch.codePointAt(0)));
|
|
1032
2089
|
}
|
|
1033
2090
|
};
|
|
1034
|
-
const
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
if (
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
continue;
|
|
1047
|
-
}
|
|
1048
|
-
if (run.footnoteRef !== void 0 || run.endnoteRef !== void 0 || run.noteNumber) {
|
|
1049
|
-
addRun({
|
|
1050
|
-
text: `${run.text}0123456789 `,
|
|
1051
|
-
properties: run.properties
|
|
1052
|
-
}, el.paragraph);
|
|
1053
|
-
continue;
|
|
2091
|
+
const addMetafile = (resource) => {
|
|
2092
|
+
const pic = resource ? imageResources.get(resource)?.metafile : void 0;
|
|
2093
|
+
for (const prim of pic?.prims ?? []) {
|
|
2094
|
+
if (prim.kind !== "text") continue;
|
|
2095
|
+
const reg = options.registry.resolveByStyle(prim.bold === true, prim.italic === true);
|
|
2096
|
+
let bucket = used.get(reg.variant);
|
|
2097
|
+
if (!bucket) {
|
|
2098
|
+
bucket = {
|
|
2099
|
+
parsed: reg.parsed,
|
|
2100
|
+
gids: /* @__PURE__ */ new Set()
|
|
2101
|
+
};
|
|
2102
|
+
used.set(reg.variant, bucket);
|
|
1054
2103
|
}
|
|
1055
|
-
|
|
2104
|
+
for (const ch of prim.text) bucket.gids.add(reg.parsed.glyphForCodepoint(ch.codePointAt(0)));
|
|
1056
2105
|
}
|
|
1057
|
-
|
|
2106
|
+
};
|
|
2107
|
+
const visit = (elements) => {
|
|
2108
|
+
for (const el of elements) if (el.kind === "paragraph") {
|
|
2109
|
+
for (const run of el.paragraph.runs) addMetafile(run.inlineImage?.resource);
|
|
2110
|
+
for (const run of displayCapsRuns(el.paragraph, options)) {
|
|
2111
|
+
if (run.math) {
|
|
2112
|
+
addMath(run.math);
|
|
2113
|
+
continue;
|
|
2114
|
+
}
|
|
2115
|
+
if (run.inlineImage && !run.text) continue;
|
|
2116
|
+
if (run.field !== void 0) {
|
|
2117
|
+
addRun({
|
|
2118
|
+
text: `${run.text}0123456789`,
|
|
2119
|
+
properties: run.properties
|
|
2120
|
+
}, el.paragraph);
|
|
2121
|
+
continue;
|
|
2122
|
+
}
|
|
2123
|
+
if (run.footnoteRef !== void 0 || run.endnoteRef !== void 0 || run.noteNumber) {
|
|
2124
|
+
addRun({
|
|
2125
|
+
text: `${run.text}0123456789 `,
|
|
2126
|
+
properties: run.properties
|
|
2127
|
+
}, el.paragraph);
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
2130
|
+
addRun(run, el.paragraph);
|
|
2131
|
+
}
|
|
2132
|
+
} else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) {
|
|
1058
2133
|
if (cell.properties.hashOnOverflow === true) for (const inner of cell.content) {
|
|
1059
2134
|
if (inner.kind !== "paragraph") continue;
|
|
1060
2135
|
for (const run of inner.paragraph.runs) addRun({
|
|
@@ -1065,8 +2140,13 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1065
2140
|
visit(cell.content);
|
|
1066
2141
|
}
|
|
1067
2142
|
else if (el.kind === "shape") {
|
|
1068
|
-
|
|
1069
|
-
|
|
2143
|
+
const shapeText = (sh) => {
|
|
2144
|
+
if (sh.text) visit(sh.text.content);
|
|
2145
|
+
for (const child of sh.children ?? []) shapeText(child.shape);
|
|
2146
|
+
};
|
|
2147
|
+
shapeText(el.shape);
|
|
2148
|
+
} else if (el.kind === "image") addMetafile(el.image.resource);
|
|
2149
|
+
else {
|
|
1070
2150
|
const chart = options.charts?.get(el.chart.chartRelId);
|
|
1071
2151
|
if (chart) {
|
|
1072
2152
|
const reg = options.registry.resolveByStyle(false, false);
|
|
@@ -1093,6 +2173,16 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1093
2173
|
}
|
|
1094
2174
|
};
|
|
1095
2175
|
visit(body);
|
|
2176
|
+
if ((options.sections ?? []).some((sec) => sec.properties.lineNumbering)) visit([{
|
|
2177
|
+
kind: "paragraph",
|
|
2178
|
+
paragraph: {
|
|
2179
|
+
properties: {},
|
|
2180
|
+
runs: [{
|
|
2181
|
+
text: "0123456789",
|
|
2182
|
+
properties: {}
|
|
2183
|
+
}]
|
|
2184
|
+
}
|
|
2185
|
+
}]);
|
|
1096
2186
|
for (const hf of headersFooters.values()) visit(hf);
|
|
1097
2187
|
for (const note of options.footnotes?.values() ?? []) visit(note);
|
|
1098
2188
|
for (const note of options.endnotes?.values() ?? []) visit(note);
|
|
@@ -1135,17 +2225,83 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1135
2225
|
}
|
|
1136
2226
|
return out;
|
|
1137
2227
|
}
|
|
2228
|
+
/**
|
|
2229
|
+
* The one line an empty paragraph still occupies — no tokens, but as tall as
|
|
2230
|
+
* the paragraph mark's own font (§17.3.1.31 `w:rPr` on the `w:pPr`, which is
|
|
2231
|
+
* exactly the run properties Word keeps for a paragraph that holds no runs).
|
|
2232
|
+
*/
|
|
2233
|
+
function emptyLine(paragraph, options, resolved, availableWidthPt, fontResources) {
|
|
2234
|
+
const mark = resolveRunProperties(paragraph.properties.runProperties ?? {}, paragraph.properties, options.styles);
|
|
2235
|
+
const fontSizePt = mark.fontSizePt;
|
|
2236
|
+
const profile = options.layoutProfile ?? "ream";
|
|
2237
|
+
const { variant } = options.registry.resolveByStyle(mark.bold, mark.italic);
|
|
2238
|
+
const parsed = fontResources.get(variant)?.parsed;
|
|
2239
|
+
const metric = profile !== "ream" && parsed ? fontLeadingPt(parsed, fontSizePt, profile) : void 0;
|
|
2240
|
+
return {
|
|
2241
|
+
tokens: [],
|
|
2242
|
+
contentWidthPt: 0,
|
|
2243
|
+
maxFontSizePt: fontSizePt,
|
|
2244
|
+
availableWidthPt,
|
|
2245
|
+
firstLine: true,
|
|
2246
|
+
resolved,
|
|
2247
|
+
isLastInParagraph: true,
|
|
2248
|
+
...metric && metric.ascentPt + metric.descentPt > 0 ? {
|
|
2249
|
+
metricHeightPt: metric.ascentPt + metric.descentPt + metric.lineGapPt,
|
|
2250
|
+
metricDescentPt: metric.descentPt
|
|
2251
|
+
} : {}
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
1138
2254
|
function layoutParagraphBlock(paragraph, options, fontResources, imageResources, contentWidth, lineWidths) {
|
|
1139
|
-
const
|
|
2255
|
+
const cascaded = resolveParagraphProperties(paragraph.properties, options.styles);
|
|
2256
|
+
const baseResolved = options.gridLinePitchPt !== void 0 && cascaded.snapToGrid !== false ? {
|
|
2257
|
+
...cascaded,
|
|
2258
|
+
gridLinePitchPt: options.gridLinePitchPt
|
|
2259
|
+
} : cascaded;
|
|
2260
|
+
if (paragraph.properties.sectionBreak === true && paragraph.runs.length === 0) return {
|
|
2261
|
+
kind: "paragraph",
|
|
2262
|
+
resolved: baseResolved,
|
|
2263
|
+
lines: [],
|
|
2264
|
+
heightPt: 0,
|
|
2265
|
+
spacingBeforePt: 0,
|
|
2266
|
+
spacingAfterPt: 0
|
|
2267
|
+
};
|
|
1140
2268
|
const baseDir = paragraphBaseDirection(paragraph, baseResolved);
|
|
1141
|
-
const
|
|
2269
|
+
const swapAlign = (a) => a === "left" ? "right" : a === "right" ? "left" : a;
|
|
2270
|
+
const resolved = baseResolved.bidi === true ? {
|
|
2271
|
+
...baseResolved,
|
|
2272
|
+
alignment: swapAlign(baseResolved.alignment),
|
|
2273
|
+
indentLeft: baseResolved.indentRight,
|
|
2274
|
+
indentRight: baseResolved.indentLeft
|
|
2275
|
+
} : baseDir === "rtl" && baseResolved.alignment === "left" ? {
|
|
1142
2276
|
...baseResolved,
|
|
1143
2277
|
alignment: "right"
|
|
1144
2278
|
} : baseResolved;
|
|
1145
|
-
const
|
|
2279
|
+
const tokens = tokenizeParagraph(paragraph, options, fontResources, imageResources, contentWidth, baseDir);
|
|
2280
|
+
const firstLineWidth = paragraphMaxWidth(resolved, contentWidth, true);
|
|
2281
|
+
const otherWidth = paragraphMaxWidth(resolved, contentWidth, false);
|
|
2282
|
+
const runWrap = (part, firstWidth, widths) => wrap(part, firstWidth, otherWidth, resolved, options.hyphenator, options.layoutProfile ?? "ream", options.doNotExpandShiftReturn === true, widths);
|
|
2283
|
+
const parts = splitAtColumnBreaks(tokens);
|
|
2284
|
+
const columnBreakLines = /* @__PURE__ */ new Set();
|
|
2285
|
+
let wrapped;
|
|
2286
|
+
if (parts.length === 1) wrapped = runWrap(tokens, firstLineWidth, lineWidths);
|
|
2287
|
+
else {
|
|
2288
|
+
wrapped = [];
|
|
2289
|
+
for (const [i, part] of parts.entries()) {
|
|
2290
|
+
const lines = runWrap(part, i === 0 ? firstLineWidth : otherWidth, lineWidths?.slice(wrapped.length));
|
|
2291
|
+
if (i > 0 && lines.length > 0) columnBreakLines.add(wrapped.length);
|
|
2292
|
+
wrapped.push(...i === 0 ? lines : lines.map((l) => ({
|
|
2293
|
+
...l,
|
|
2294
|
+
firstLine: false
|
|
2295
|
+
})));
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
const lines = wrapped.length > 0 ? wrapped : [emptyLine(paragraph, options, resolved, Math.max(firstLineWidth, 0), fontResources)];
|
|
1146
2299
|
let heightPt = 0;
|
|
1147
2300
|
for (const line of lines) heightPt += computeLineHeight(line, resolved);
|
|
1148
2301
|
const numbering = paragraph.properties.numbering;
|
|
2302
|
+
const breakIdx = paragraph.runs.findIndex((r) => r.pageBreak === true);
|
|
2303
|
+
const leadingPageBreak = breakIdx >= 0 && paragraph.runs.slice(0, breakIdx + 1).every((r) => r.text === "" && r.inlineImage === void 0);
|
|
2304
|
+
const trailingPageBreak = paragraph.runs.some((r, i) => r.pageBreak === true && (!leadingPageBreak || i > breakIdx));
|
|
1149
2305
|
return {
|
|
1150
2306
|
kind: "paragraph",
|
|
1151
2307
|
resolved,
|
|
@@ -1157,7 +2313,9 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
|
|
|
1157
2313
|
numId: numbering.numId,
|
|
1158
2314
|
level: numbering.ilvl
|
|
1159
2315
|
} } : {},
|
|
1160
|
-
...
|
|
2316
|
+
...leadingPageBreak ? { pageBreakBefore: true } : {},
|
|
2317
|
+
...trailingPageBreak ? { pageBreakAfter: true } : {},
|
|
2318
|
+
...columnBreakLines.size > 0 ? { columnBreakLines } : {},
|
|
1161
2319
|
...paragraph.bookmarks && paragraph.bookmarks.length > 0 ? { bookmarks: paragraph.bookmarks } : {},
|
|
1162
2320
|
source: paragraph
|
|
1163
2321
|
};
|
|
@@ -1197,13 +2355,76 @@ function resolveMathItems(items, fontFor) {
|
|
|
1197
2355
|
};
|
|
1198
2356
|
});
|
|
1199
2357
|
}
|
|
2358
|
+
/**
|
|
2359
|
+
* §17.3.2.5 `w:caps` / §17.3.2.33 `w:smallCaps` — the paragraph's runs as they
|
|
2360
|
+
* are DISPLAYED. A capitals run is upper-cased; a small-capitals run is too,
|
|
2361
|
+
* and the letters that were lower case are split out to be set smaller.
|
|
2362
|
+
*
|
|
2363
|
+
* @param paragraph The paragraph.
|
|
2364
|
+
* @param options The render options (for the style cascade).
|
|
2365
|
+
* @returns The runs to lay out — the same array when none asks for capitals.
|
|
2366
|
+
*/
|
|
2367
|
+
function displayCapsRuns(paragraph, options) {
|
|
2368
|
+
let any = false;
|
|
2369
|
+
const out = [];
|
|
2370
|
+
for (const raw of paragraph.runs) {
|
|
2371
|
+
const run = raw.text.includes(SOFT_HYPHEN) ? (any = true, {
|
|
2372
|
+
...raw,
|
|
2373
|
+
text: raw.text.replaceAll(SOFT_HYPHEN, "")
|
|
2374
|
+
}) : raw;
|
|
2375
|
+
const resolved = run.text.length > 0 ? resolveRunProperties(run.properties, paragraph.properties, options.styles) : void 0;
|
|
2376
|
+
if (!resolved || !resolved.caps && !resolved.smallCaps) {
|
|
2377
|
+
out.push(run);
|
|
2378
|
+
continue;
|
|
2379
|
+
}
|
|
2380
|
+
any = true;
|
|
2381
|
+
if (resolved.caps) {
|
|
2382
|
+
out.push({
|
|
2383
|
+
...run,
|
|
2384
|
+
text: run.text.toUpperCase()
|
|
2385
|
+
});
|
|
2386
|
+
continue;
|
|
2387
|
+
}
|
|
2388
|
+
let start = 0;
|
|
2389
|
+
const wasLower = (i) => {
|
|
2390
|
+
const ch = run.text[i];
|
|
2391
|
+
return ch.toLowerCase() === ch && ch.toUpperCase() !== ch;
|
|
2392
|
+
};
|
|
2393
|
+
for (let i = 1; i <= run.text.length; i++) {
|
|
2394
|
+
if (i < run.text.length && wasLower(i) === wasLower(start)) continue;
|
|
2395
|
+
const segment = run.text.slice(start, i).toUpperCase();
|
|
2396
|
+
out.push(wasLower(start) ? {
|
|
2397
|
+
...run,
|
|
2398
|
+
text: segment,
|
|
2399
|
+
properties: {
|
|
2400
|
+
...run.properties,
|
|
2401
|
+
fontSizePt: pt(resolved.fontSizePt * .8)
|
|
2402
|
+
}
|
|
2403
|
+
} : {
|
|
2404
|
+
...run,
|
|
2405
|
+
text: segment
|
|
2406
|
+
});
|
|
2407
|
+
start = i;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
return any ? out : paragraph.runs;
|
|
2411
|
+
}
|
|
1200
2412
|
function tokenizeParagraph(paragraph, options, fontResources, imageResources, contentWidth, baseDir) {
|
|
1201
|
-
const plans = paragraph.
|
|
2413
|
+
const plans = displayCapsRuns(paragraph, options).map((run) => {
|
|
1202
2414
|
if (run.inlineImage) {
|
|
1203
|
-
const
|
|
2415
|
+
const eff = run.inlineImage.effectExtent;
|
|
2416
|
+
const padW = (eff?.leftPt ?? 0) + (eff?.rightPt ?? 0);
|
|
2417
|
+
const padH = (eff?.topPt ?? 0) + (eff?.bottomPt ?? 0);
|
|
2418
|
+
const naturalW = run.inlineImage.width + padW;
|
|
1204
2419
|
const widthPt = Math.min(naturalW, contentWidth);
|
|
1205
2420
|
const scale = naturalW > 0 ? widthPt / naturalW : 1;
|
|
1206
|
-
const heightPt = run.inlineImage.height * scale;
|
|
2421
|
+
const heightPt = (run.inlineImage.height + padH) * scale;
|
|
2422
|
+
const drawBox = eff ? {
|
|
2423
|
+
dxPt: eff.leftPt * scale,
|
|
2424
|
+
dyPt: eff.bottomPt * scale,
|
|
2425
|
+
widthPt: run.inlineImage.width * scale,
|
|
2426
|
+
heightPt: run.inlineImage.height * scale
|
|
2427
|
+
} : void 0;
|
|
1207
2428
|
const res = run.inlineImage.resource ? imageResources?.get(run.inlineImage.resource) : void 0;
|
|
1208
2429
|
const resolvedRun = resolveRunProperties(run.properties, paragraph.properties, options.styles);
|
|
1209
2430
|
const { fontKey } = runFontKeyAndParsed(options, resolvedRun.fontFamily.ascii, resolvedRun.bold, resolvedRun.italic);
|
|
@@ -1215,7 +2436,16 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
|
|
|
1215
2436
|
isImage: true,
|
|
1216
2437
|
imageWidthPt: widthPt,
|
|
1217
2438
|
imageHeightPt: heightPt,
|
|
1218
|
-
imageResourceName: res?.resourceName ?? ""
|
|
2439
|
+
imageResourceName: res?.resourceName ?? "",
|
|
2440
|
+
...res?.metafile ? { imageMetafile: metafileDrawing(res.metafile, drawBox?.widthPt ?? widthPt, drawBox?.heightPt ?? heightPt, options, fontResources) } : {},
|
|
2441
|
+
...run.inlineImage.outline ? { imageOutline: run.inlineImage.outline } : {},
|
|
2442
|
+
...run.inlineImage.shadow ? { imageShadow: run.inlineImage.shadow } : {},
|
|
2443
|
+
...run.inlineImage.wash ? { imageWash: run.inlineImage.wash } : {},
|
|
2444
|
+
...run.inlineImage.crop ? { imageCrop: run.inlineImage.crop } : {},
|
|
2445
|
+
...run.inlineImage.rotation60k ? { imageRotationDeg: run.inlineImage.rotation60k / 6e4 } : {},
|
|
2446
|
+
...run.inlineImage.flipH ? { imageFlipH: true } : {},
|
|
2447
|
+
...run.inlineImage.flipV ? { imageFlipV: true } : {},
|
|
2448
|
+
...drawBox ? { imageDrawBox: drawBox } : {}
|
|
1219
2449
|
};
|
|
1220
2450
|
}
|
|
1221
2451
|
if (run.math) {
|
|
@@ -1270,6 +2500,19 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
|
|
|
1270
2500
|
...plan.resolvedRun.rtl ? { rtl: true } : {}
|
|
1271
2501
|
})), baseDir));
|
|
1272
2502
|
}
|
|
2503
|
+
/**
|
|
2504
|
+
* Split a paragraph's tokens at every §17.3.3.1 column break, dropping the
|
|
2505
|
+
* break token itself (its line ends there anyway). One part ⇒ no break.
|
|
2506
|
+
*
|
|
2507
|
+
* @param tokens The paragraph's tokens in order.
|
|
2508
|
+
* @returns The parts, in order; always at least one.
|
|
2509
|
+
*/
|
|
2510
|
+
function splitAtColumnBreaks(tokens) {
|
|
2511
|
+
const parts = [[]];
|
|
2512
|
+
for (const t of tokens) if (t.kind === "text" && t.columnBreak) parts.push([]);
|
|
2513
|
+
else parts[parts.length - 1].push(t);
|
|
2514
|
+
return parts.filter((p, i) => i === 0 || p.length > 0);
|
|
2515
|
+
}
|
|
1273
2516
|
function tokenizePlansLtr(plans) {
|
|
1274
2517
|
const tokens = [];
|
|
1275
2518
|
for (const plan of plans) {
|
|
@@ -1279,6 +2522,15 @@ function tokenizePlansLtr(plans) {
|
|
|
1279
2522
|
imageResourceName: plan.imageResourceName,
|
|
1280
2523
|
widthPt: plan.imageWidthPt,
|
|
1281
2524
|
heightPt: plan.imageHeightPt,
|
|
2525
|
+
...plan.imageOutline ? { outline: plan.imageOutline } : {},
|
|
2526
|
+
...plan.imageShadow ? { shadow: plan.imageShadow } : {},
|
|
2527
|
+
...plan.imageWash ? { wash: plan.imageWash } : {},
|
|
2528
|
+
...plan.imageMetafile ? { metafile: plan.imageMetafile } : {},
|
|
2529
|
+
...plan.imageCrop ? { crop: plan.imageCrop } : {},
|
|
2530
|
+
...plan.imageRotationDeg ? { rotationDeg: plan.imageRotationDeg } : {},
|
|
2531
|
+
...plan.imageFlipH ? { flipH: true } : {},
|
|
2532
|
+
...plan.imageFlipV ? { flipV: true } : {},
|
|
2533
|
+
...plan.imageDrawBox ? { drawBox: plan.imageDrawBox } : {},
|
|
1282
2534
|
isSpace: false,
|
|
1283
2535
|
bidiLevel: 0
|
|
1284
2536
|
});
|
|
@@ -1301,21 +2553,37 @@ function tokenizePlansLtr(plans) {
|
|
|
1301
2553
|
kind: "text",
|
|
1302
2554
|
text: t.text,
|
|
1303
2555
|
isSpace: t.isSpace,
|
|
2556
|
+
...t.tab ? { tab: true } : {},
|
|
1304
2557
|
...plan.run.href !== void 0 ? { href: plan.run.href } : {},
|
|
1305
2558
|
...plan.run.footnoteRef !== void 0 ? { footnoteRef: plan.run.footnoteRef } : {},
|
|
1306
2559
|
...plan.run.anchor !== void 0 ? { anchor: plan.run.anchor } : {},
|
|
1307
2560
|
...plan.run.listMarker ? { listMarker: true } : {},
|
|
2561
|
+
...plan.run.columnBreak ? { columnBreak: true } : {},
|
|
1308
2562
|
...highlight ? { highlight: true } : {},
|
|
1309
2563
|
resolvedRun: plan.resolvedRun,
|
|
1310
2564
|
font: plan.font,
|
|
1311
2565
|
fontSizePt: plan.fontSizePt,
|
|
1312
2566
|
...plan.risePt !== void 0 ? { risePt: plan.risePt } : {},
|
|
1313
|
-
widthPt: plan
|
|
2567
|
+
widthPt: measureRunText(plan, t.text),
|
|
1314
2568
|
bidiLevel: 0
|
|
1315
2569
|
});
|
|
1316
2570
|
}
|
|
1317
2571
|
return tokens;
|
|
1318
2572
|
}
|
|
2573
|
+
/**
|
|
2574
|
+
* §17.3.2.35 — a run's text measured with the space it puts between its
|
|
2575
|
+
* characters. PDF adds the same amount per glyph through `Tc`, so the width the
|
|
2576
|
+
* line is broken at and the width the page draws agree.
|
|
2577
|
+
*
|
|
2578
|
+
* @param plan The run being tokenized.
|
|
2579
|
+
* @param text The token's text.
|
|
2580
|
+
* @returns The advance width in points.
|
|
2581
|
+
*/
|
|
2582
|
+
function measureRunText(plan, text) {
|
|
2583
|
+
const base = plan.font.measure.textWidthPt(text, plan.fontSizePt);
|
|
2584
|
+
const extra = plan.resolvedRun.letterSpacingPt;
|
|
2585
|
+
return extra === void 0 || extra === 0 ? base : base + extra * [...text].length;
|
|
2586
|
+
}
|
|
1319
2587
|
function tokenizePlansBidi(plans, realLevels) {
|
|
1320
2588
|
const tokens = [];
|
|
1321
2589
|
let realIdx = 0;
|
|
@@ -1327,6 +2595,15 @@ function tokenizePlansBidi(plans, realLevels) {
|
|
|
1327
2595
|
imageResourceName: plan.imageResourceName,
|
|
1328
2596
|
widthPt: plan.imageWidthPt,
|
|
1329
2597
|
heightPt: plan.imageHeightPt,
|
|
2598
|
+
...plan.imageOutline ? { outline: plan.imageOutline } : {},
|
|
2599
|
+
...plan.imageShadow ? { shadow: plan.imageShadow } : {},
|
|
2600
|
+
...plan.imageWash ? { wash: plan.imageWash } : {},
|
|
2601
|
+
...plan.imageMetafile ? { metafile: plan.imageMetafile } : {},
|
|
2602
|
+
...plan.imageCrop ? { crop: plan.imageCrop } : {},
|
|
2603
|
+
...plan.imageRotationDeg ? { rotationDeg: plan.imageRotationDeg } : {},
|
|
2604
|
+
...plan.imageFlipH ? { flipH: true } : {},
|
|
2605
|
+
...plan.imageFlipV ? { flipV: true } : {},
|
|
2606
|
+
...plan.imageDrawBox ? { drawBox: plan.imageDrawBox } : {},
|
|
1330
2607
|
isSpace: false,
|
|
1331
2608
|
bidiLevel: realLevels[realIdx] ?? 0
|
|
1332
2609
|
});
|
|
@@ -1361,12 +2638,13 @@ function tokenizePlansBidi(plans, realLevels) {
|
|
|
1361
2638
|
...plan.run.footnoteRef !== void 0 ? { footnoteRef: plan.run.footnoteRef } : {},
|
|
1362
2639
|
...plan.run.anchor !== void 0 ? { anchor: plan.run.anchor } : {},
|
|
1363
2640
|
...plan.run.listMarker ? { listMarker: true } : {},
|
|
2641
|
+
...plan.run.columnBreak ? { columnBreak: true } : {},
|
|
1364
2642
|
...(plan.run.commentRangeRefs?.length ?? 0) > 0 ? { highlight: true } : {},
|
|
1365
2643
|
resolvedRun: plan.resolvedRun,
|
|
1366
2644
|
font: plan.font,
|
|
1367
2645
|
fontSizePt: plan.fontSizePt,
|
|
1368
2646
|
...plan.risePt !== void 0 ? { risePt: plan.risePt } : {},
|
|
1369
|
-
widthPt: plan
|
|
2647
|
+
widthPt: measureRunText(plan, text),
|
|
1370
2648
|
bidiLevel: curLevel
|
|
1371
2649
|
});
|
|
1372
2650
|
};
|
|
@@ -1393,6 +2671,7 @@ function paragraphBaseDirection(paragraph, resolved) {
|
|
|
1393
2671
|
if (resolved.bidi) return "rtl";
|
|
1394
2672
|
let text = "";
|
|
1395
2673
|
for (const run of paragraph.runs) {
|
|
2674
|
+
if (run.listMarker === true) continue;
|
|
1396
2675
|
if (!run.inlineImage) text += run.text;
|
|
1397
2676
|
if (text.length > 64) break;
|
|
1398
2677
|
}
|
|
@@ -1405,16 +2684,29 @@ function paragraphMaxWidth(p, contentWidth, firstLine) {
|
|
|
1405
2684
|
const firstLineExtra = firstLine ? p.indentFirstLine : 0;
|
|
1406
2685
|
return Math.max(1, contentWidth - indentLeft - indentRight - firstLineExtra);
|
|
1407
2686
|
}
|
|
2687
|
+
var HYPHENS = new Set([
|
|
2688
|
+
45,
|
|
2689
|
+
8208,
|
|
2690
|
+
8211,
|
|
2691
|
+
8212
|
|
2692
|
+
]);
|
|
2693
|
+
/** §17.3.3.29 — the optional hyphen, printed only where a line breaks on it. */
|
|
2694
|
+
var SOFT_HYPHEN = "";
|
|
1408
2695
|
function tokenizeText(text) {
|
|
1409
2696
|
const out = [];
|
|
1410
2697
|
if (text.length === 0) return out;
|
|
1411
|
-
const re = /(\s+)|(\S+)/g;
|
|
2698
|
+
const re = /(\t)|(\s+)|(\S+)/g;
|
|
1412
2699
|
let m;
|
|
1413
2700
|
while ((m = re.exec(text)) !== null) if (m[1] !== void 0) out.push({
|
|
1414
|
-
text:
|
|
2701
|
+
text: "",
|
|
2702
|
+
isSpace: true,
|
|
2703
|
+
tab: true
|
|
2704
|
+
});
|
|
2705
|
+
else if (m[2] !== void 0) out.push({
|
|
2706
|
+
text: m[2],
|
|
1415
2707
|
isSpace: true
|
|
1416
2708
|
});
|
|
1417
|
-
else if (m[
|
|
2709
|
+
else if (m[3] !== void 0) for (const piece of splitCjkSegment(m[3])) out.push({
|
|
1418
2710
|
text: piece,
|
|
1419
2711
|
isSpace: false
|
|
1420
2712
|
});
|
|
@@ -1428,14 +2720,51 @@ function lastCodePoint(s) {
|
|
|
1428
2720
|
function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
1429
2721
|
const entries = [];
|
|
1430
2722
|
let prevBoxEndCp = null;
|
|
2723
|
+
let prevWasImage = false;
|
|
1431
2724
|
for (const tok of tokens) {
|
|
2725
|
+
const breaks = tok.isSpace ? tok.text.match(/\r\n|[\r\n]/gu)?.length ?? 0 : 0;
|
|
2726
|
+
if (breaks > 0) {
|
|
2727
|
+
for (let i = 0; i < breaks; i++) {
|
|
2728
|
+
entries.push({
|
|
2729
|
+
item: {
|
|
2730
|
+
type: "glue",
|
|
2731
|
+
width: 0,
|
|
2732
|
+
stretch: 1e6,
|
|
2733
|
+
shrink: 0
|
|
2734
|
+
},
|
|
2735
|
+
token: null
|
|
2736
|
+
});
|
|
2737
|
+
entries.push({
|
|
2738
|
+
item: {
|
|
2739
|
+
type: "penalty",
|
|
2740
|
+
width: 0,
|
|
2741
|
+
penalty: FORCED_BREAK,
|
|
2742
|
+
flagged: false
|
|
2743
|
+
},
|
|
2744
|
+
token: null,
|
|
2745
|
+
softBreak: true
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
prevBoxEndCp = null;
|
|
2749
|
+
continue;
|
|
2750
|
+
}
|
|
1432
2751
|
if (tok.isSpace || tok.kind === "image" || tok.kind === "math") {
|
|
2752
|
+
if (tok.kind === "image" && (prevWasImage || prevBoxEndCp !== null)) entries.push({
|
|
2753
|
+
item: {
|
|
2754
|
+
type: "penalty",
|
|
2755
|
+
width: 0,
|
|
2756
|
+
penalty: 0,
|
|
2757
|
+
flagged: false
|
|
2758
|
+
},
|
|
2759
|
+
token: null
|
|
2760
|
+
});
|
|
2761
|
+
prevWasImage = tok.kind === "image";
|
|
1433
2762
|
entries.push({
|
|
1434
2763
|
item: tok.isSpace ? {
|
|
1435
2764
|
type: "glue",
|
|
1436
2765
|
width: tok.widthPt,
|
|
1437
2766
|
stretch: tok.widthPt * .6,
|
|
1438
|
-
shrink: tok.widthPt *
|
|
2767
|
+
shrink: tok.widthPt * GLUE_SHRINK_RATIO
|
|
1439
2768
|
} : {
|
|
1440
2769
|
type: "box",
|
|
1441
2770
|
width: tok.widthPt
|
|
@@ -1446,7 +2775,7 @@ function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
|
1446
2775
|
continue;
|
|
1447
2776
|
}
|
|
1448
2777
|
const startCp = tok.text.codePointAt(0) ?? 0;
|
|
1449
|
-
if (prevBoxEndCp !== null && cjkBreakBetween(prevBoxEndCp, startCp)) entries.push({
|
|
2778
|
+
if (prevWasImage || prevBoxEndCp !== null && cjkBreakBetween(prevBoxEndCp, startCp)) entries.push({
|
|
1450
2779
|
item: {
|
|
1451
2780
|
type: "penalty",
|
|
1452
2781
|
width: 0,
|
|
@@ -1455,10 +2784,12 @@ function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
|
1455
2784
|
},
|
|
1456
2785
|
token: null
|
|
1457
2786
|
});
|
|
2787
|
+
prevWasImage = false;
|
|
1458
2788
|
prevBoxEndCp = lastCodePoint(tok.text);
|
|
1459
2789
|
const positions = hyphenator ? hyphenator.hyphenate(tok.text) : [];
|
|
1460
2790
|
if (positions.length === 0) {
|
|
1461
|
-
const
|
|
2791
|
+
const tooWide = maxLineWidthPt !== void 0 && maxLineWidthPt > 0;
|
|
2792
|
+
const chunks = (tooWide && tok.widthPt > maxLineWidthPt ? splitAtHyphens(tok) : void 0) ?? (tooWide && tok.widthPt > maxLineWidthPt * 1.1 ? splitToWidth(tok, maxLineWidthPt) : void 0);
|
|
1462
2793
|
if (chunks) {
|
|
1463
2794
|
chunks.forEach((chunk, ci) => {
|
|
1464
2795
|
if (ci > 0) entries.push({
|
|
@@ -1545,6 +2876,94 @@ function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
|
1545
2876
|
});
|
|
1546
2877
|
return entries;
|
|
1547
2878
|
}
|
|
2879
|
+
/** Word's own default when a document names none (§17.15.1.25): half an inch. */
|
|
2880
|
+
var DEFAULT_TAB_STOP_PT = 36;
|
|
2881
|
+
var TAB_LEADER_CHARS = new Map([
|
|
2882
|
+
["dot", "."],
|
|
2883
|
+
["hyphen", "-"],
|
|
2884
|
+
["underscore", "_"],
|
|
2885
|
+
["middleDot", "·"]
|
|
2886
|
+
]);
|
|
2887
|
+
/**
|
|
2888
|
+
* Give each tab on a line the width that carries it to its stop, and the
|
|
2889
|
+
* leader that fills the way there.
|
|
2890
|
+
*
|
|
2891
|
+
* §17.3.1.37 — a tab advances to a POSITION, not by a fixed amount, and where
|
|
2892
|
+
* that position is depends on how far the line has already run. Measured as
|
|
2893
|
+
* ordinary whitespace it collapsed to a space: the page numbers of
|
|
2894
|
+
* FDO77715.docx's index sat against their titles with no dot leader between,
|
|
2895
|
+
* and its header ran its left and right halves together on one line.
|
|
2896
|
+
*
|
|
2897
|
+
* A `right`, `center` or `decimal` stop positions the text AFTER the tab, so
|
|
2898
|
+
* each is resolved against the segment that follows it.
|
|
2899
|
+
*
|
|
2900
|
+
* @param tokens The line's tokens, replaced in place.
|
|
2901
|
+
* @param resolved The paragraph's resolved properties: the stops and indents.
|
|
2902
|
+
* @param isFirst Whether this is the paragraph's first line.
|
|
2903
|
+
*/
|
|
2904
|
+
function resolveTabs(tokens, resolved, isFirst, availableWidthPt) {
|
|
2905
|
+
const stops = resolved.tabs;
|
|
2906
|
+
let x = resolved.indentLeft + (isFirst ? resolved.indentFirstLine : 0);
|
|
2907
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2908
|
+
const tok = tokens[i];
|
|
2909
|
+
if (!tok) continue;
|
|
2910
|
+
if (!(tok.kind === "text" && tok.tab === true)) {
|
|
2911
|
+
x += tok.widthPt;
|
|
2912
|
+
continue;
|
|
2913
|
+
}
|
|
2914
|
+
let segment = 0;
|
|
2915
|
+
let decimalAt = 0;
|
|
2916
|
+
let seenDecimal = false;
|
|
2917
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
2918
|
+
const next = tokens[j];
|
|
2919
|
+
if (next.kind === "text" && next.tab === true) break;
|
|
2920
|
+
if (!seenDecimal && next.kind === "text") {
|
|
2921
|
+
const dot = next.text.indexOf(".");
|
|
2922
|
+
if (dot >= 0) {
|
|
2923
|
+
decimalAt = segment + next.font.measure.textWidthPt(next.text.slice(0, dot), next.fontSizePt);
|
|
2924
|
+
seenDecimal = true;
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
segment += next.widthPt;
|
|
2928
|
+
}
|
|
2929
|
+
const stopAt = (sp) => sp.relativeTo === "center" ? availableWidthPt / 2 : sp.relativeTo === "right" ? availableWidthPt : sp.positionPt;
|
|
2930
|
+
const stop = stops.find((sp) => stopAt(sp) > x + .01);
|
|
2931
|
+
const hangingStop = isFirst && resolved.indentFirstLine < 0 && resolved.indentLeft > x + .01 ? resolved.indentLeft : void 0;
|
|
2932
|
+
const explicitAt = stop ? stopAt(stop) : void 0;
|
|
2933
|
+
const position = explicitAt !== void 0 && (hangingStop === void 0 || explicitAt <= hangingStop) ? explicitAt : hangingStop ?? (Math.floor(x / DEFAULT_TAB_STOP_PT) + 1) * DEFAULT_TAB_STOP_PT;
|
|
2934
|
+
const alignment = stop?.alignment ?? "left";
|
|
2935
|
+
if (segment === 0 && alignment !== "left") {
|
|
2936
|
+
const after = tokens[i + 1];
|
|
2937
|
+
if (after?.kind === "text" && after.tab === true) {
|
|
2938
|
+
tokens[i] = {
|
|
2939
|
+
...tok,
|
|
2940
|
+
widthPt: 0,
|
|
2941
|
+
text: ""
|
|
2942
|
+
};
|
|
2943
|
+
continue;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
let target = position;
|
|
2947
|
+
if (alignment === "right") target = position - segment;
|
|
2948
|
+
else if (alignment === "center") target = position - segment / 2;
|
|
2949
|
+
else if (alignment === "decimal") target = position - (seenDecimal ? decimalAt : segment);
|
|
2950
|
+
const width = Math.max(0, target - x);
|
|
2951
|
+
const leaderChar = stop?.leader === void 0 ? void 0 : TAB_LEADER_CHARS.get(stop.leader);
|
|
2952
|
+
const text = leaderChar === void 0 ? "" : fillLeader(tok, leaderChar, width);
|
|
2953
|
+
tokens[i] = {
|
|
2954
|
+
...tok,
|
|
2955
|
+
widthPt: width,
|
|
2956
|
+
text
|
|
2957
|
+
};
|
|
2958
|
+
x += width;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
/** As many leader characters as fit the gap, measured in the tab's own font. */
|
|
2962
|
+
function fillLeader(tab, leader, widthPt) {
|
|
2963
|
+
const unit = tab.font.measure.textWidthPt(leader, tab.fontSizePt);
|
|
2964
|
+
if (!(unit > 0)) return "";
|
|
2965
|
+
return leader.repeat(Math.max(0, Math.floor(widthPt / unit)));
|
|
2966
|
+
}
|
|
1548
2967
|
/**
|
|
1549
2968
|
* Cut an unbreakable word into the widest pieces that fit `widthPt`, measured
|
|
1550
2969
|
* with the token's own font. Each piece keeps the run's formatting, so the line
|
|
@@ -1555,6 +2974,24 @@ function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
|
1555
2974
|
* @returns The pieces in order, or undefined when even one character overruns
|
|
1556
2975
|
* (nothing can be gained by splitting then).
|
|
1557
2976
|
*/
|
|
2977
|
+
function splitAtHyphens(tok) {
|
|
2978
|
+
if (tok.kind !== "text") return void 0;
|
|
2979
|
+
const parts = [];
|
|
2980
|
+
let start = 0;
|
|
2981
|
+
for (let i = 0; i < tok.text.length; i++) {
|
|
2982
|
+
if (!HYPHENS.has(tok.text.codePointAt(i) ?? 0)) continue;
|
|
2983
|
+
if (i === start || i === tok.text.length - 1) continue;
|
|
2984
|
+
parts.push(tok.text.slice(start, i + 1));
|
|
2985
|
+
start = i + 1;
|
|
2986
|
+
}
|
|
2987
|
+
if (parts.length === 0) return void 0;
|
|
2988
|
+
parts.push(tok.text.slice(start));
|
|
2989
|
+
return parts.map((text) => ({
|
|
2990
|
+
...tok,
|
|
2991
|
+
text,
|
|
2992
|
+
widthPt: tok.font.measure.textWidthPt(text, tok.fontSizePt)
|
|
2993
|
+
}));
|
|
2994
|
+
}
|
|
1558
2995
|
function splitToWidth(tok, widthPt) {
|
|
1559
2996
|
if (tok.kind !== "text") return void 0;
|
|
1560
2997
|
const chars = [...tok.text];
|
|
@@ -1585,18 +3022,19 @@ function splitToWidth(tok, widthPt) {
|
|
|
1585
3022
|
}
|
|
1586
3023
|
/** The fewest characters a mid-word break may leave on a line. */
|
|
1587
3024
|
var MIN_SPLIT_CHARS = 6;
|
|
1588
|
-
function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved, profile) {
|
|
3025
|
+
function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved, profile, allowEmpty = false) {
|
|
1589
3026
|
let st = start;
|
|
1590
3027
|
let et = breakIdx;
|
|
1591
3028
|
while (st < et && (entries[st].token === null || !isFirst && entries[st].token?.isSpace === true)) st++;
|
|
1592
3029
|
const paints = (t) => t !== null && t !== void 0 && t.kind === "text" && (t.resolvedRun.underline !== "none" || t.resolvedRun.strike);
|
|
1593
3030
|
while (et > st && (entries[et - 1].token === null || entries[et - 1].token?.isSpace === true && !paints(entries[et - 1].token))) et--;
|
|
1594
|
-
if (st >= et) return null;
|
|
3031
|
+
if (st >= et && !allowEmpty) return null;
|
|
1595
3032
|
const lineTokens = [];
|
|
1596
3033
|
for (let i = st; i < et; i++) {
|
|
1597
3034
|
const ft = entries[i].token;
|
|
1598
3035
|
if (ft) lineTokens.push(ft);
|
|
1599
3036
|
}
|
|
3037
|
+
resolveTabs(lineTokens, resolved, isFirst, availableWidthPt);
|
|
1600
3038
|
const hyphenWidth = entries[breakIdx]?.hyphenWidthPt;
|
|
1601
3039
|
if (hyphenWidth !== void 0) {
|
|
1602
3040
|
const lastIdx = lineTokens.length - 1;
|
|
@@ -1616,8 +3054,10 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
|
|
|
1616
3054
|
let metricAscent = 0;
|
|
1617
3055
|
let metricDescent = 0;
|
|
1618
3056
|
let metricLineGap = 0;
|
|
3057
|
+
const prints = lineTokens.some((t) => !t.isSpace);
|
|
1619
3058
|
for (const t of lineTokens) {
|
|
1620
3059
|
contentWidth += t.widthPt;
|
|
3060
|
+
if (prints && t.isSpace) continue;
|
|
1621
3061
|
const sz = tokenLineSize(t);
|
|
1622
3062
|
if (sz > maxSize) maxSize = sz;
|
|
1623
3063
|
if (t.kind === "math") {
|
|
@@ -1647,10 +3087,26 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
|
|
|
1647
3087
|
} : {}
|
|
1648
3088
|
};
|
|
1649
3089
|
}
|
|
1650
|
-
function
|
|
3090
|
+
function withLeadingTabWidths(tokens, resolved, firstLineWidth) {
|
|
3091
|
+
const first = tokens[0];
|
|
3092
|
+
if (!(first?.kind === "text" && first.tab === true)) return tokens;
|
|
3093
|
+
const probe = tokens.map((t) => ({ ...t }));
|
|
3094
|
+
resolveTabs(probe, resolved, true, firstLineWidth);
|
|
3095
|
+
const out = tokens.slice();
|
|
3096
|
+
for (let i = 0; i < out.length; i++) {
|
|
3097
|
+
const tok = out[i];
|
|
3098
|
+
if (!(tok.kind === "text" && tok.tab === true)) break;
|
|
3099
|
+
out[i] = {
|
|
3100
|
+
...tok,
|
|
3101
|
+
widthPt: probe[i].widthPt
|
|
3102
|
+
};
|
|
3103
|
+
}
|
|
3104
|
+
return out;
|
|
3105
|
+
}
|
|
3106
|
+
function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile, noExpandShiftReturn, lineWidths) {
|
|
1651
3107
|
if (tokens.length === 0) return [];
|
|
1652
3108
|
const widths = lineWidths ?? [firstLineWidth, otherWidth];
|
|
1653
|
-
const entries = paragraphItemStream(tokens, hyphenator, Math.max(0, ...widths));
|
|
3109
|
+
const entries = paragraphItemStream(withLeadingTabWidths(tokens, resolved, widths[0] ?? firstLineWidth), hyphenator, Math.max(0, ...widths));
|
|
1654
3110
|
const items = entries.map((e) => e.item);
|
|
1655
3111
|
const breaks = profile === "ream" ? breakLines(items, widths).breaks : greedyBreakLines(items, widths);
|
|
1656
3112
|
const buildLines = (at) => {
|
|
@@ -1658,10 +3114,13 @@ function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile,
|
|
|
1658
3114
|
let start = 0;
|
|
1659
3115
|
let isFirst = true;
|
|
1660
3116
|
let lineIdx = 0;
|
|
1661
|
-
for (const breakIdx of at) {
|
|
3117
|
+
for (const [i, breakIdx] of at.entries()) {
|
|
1662
3118
|
const width = lineIdx < widths.length ? widths[lineIdx] : widths[widths.length - 1];
|
|
1663
|
-
const line = lineFromRange(entries, start, breakIdx, width, isFirst, resolved, profile);
|
|
1664
|
-
if (line) out.push(
|
|
3119
|
+
const line = lineFromRange(entries, start, breakIdx, width, isFirst, resolved, profile, i < at.length - 1);
|
|
3120
|
+
if (line) out.push(noExpandShiftReturn && entries[breakIdx]?.softBreak ? {
|
|
3121
|
+
...line,
|
|
3122
|
+
noJustify: true
|
|
3123
|
+
} : line);
|
|
1665
3124
|
start = breakIdx + 1;
|
|
1666
3125
|
isFirst = false;
|
|
1667
3126
|
lineIdx++;
|
|
@@ -1695,11 +3154,17 @@ function fontLeadingPt(parsed, sizePt, profile) {
|
|
|
1695
3154
|
};
|
|
1696
3155
|
}
|
|
1697
3156
|
function computeLineHeight(line, p) {
|
|
3157
|
+
const natural = naturalLineHeight(line, p);
|
|
3158
|
+
const pitch = p.gridLinePitchPt;
|
|
3159
|
+
if (pitch !== void 0 && pitch > 0) return Math.ceil(natural / pitch - 1e-6) * pitch;
|
|
3160
|
+
return natural;
|
|
3161
|
+
}
|
|
3162
|
+
function naturalLineHeight(line, p) {
|
|
1698
3163
|
const fontSize = line.maxFontSizePt || 12;
|
|
1699
3164
|
const natural = line.metricHeightPt ?? fontSize * 1.2;
|
|
1700
3165
|
const mathH = (line.mathAscentPt ?? 0) + (line.mathDescentPt ?? 0);
|
|
1701
3166
|
const mathNeed = mathH > 0 ? mathH * 1.05 : 0;
|
|
1702
|
-
if (p.spacingLineRule === "exact" && p.spacingLine
|
|
3167
|
+
if (p.spacingLineRule === "exact" && p.spacingLine >= 0) return Math.max(p.spacingLine, mathNeed);
|
|
1703
3168
|
if (p.spacingLineRule === "atLeast" && p.spacingLine > 0) return Math.max(natural, p.spacingLine, mathNeed);
|
|
1704
3169
|
const lineTwips = Math.round(p.spacingLine * 20);
|
|
1705
3170
|
const multiple = lineTwips > 0 ? lineTwips / 240 : 1;
|
|
@@ -1710,6 +3175,37 @@ function lineDescent(line) {
|
|
|
1710
3175
|
const base = line.metricDescentPt ?? fs * .2;
|
|
1711
3176
|
return Math.max(base, line.mathDescentPt ?? 0);
|
|
1712
3177
|
}
|
|
3178
|
+
/**
|
|
3179
|
+
* §17.3.1.33 — how far the baseline stands above the BOTTOM of the line box,
|
|
3180
|
+
* which is what says where the extra height of a spaced-out line goes.
|
|
3181
|
+
*
|
|
3182
|
+
* A line given a fixed height (`exact`, `atLeast`) hangs from the bottom of its
|
|
3183
|
+
* box: the space a taller `w:line` asks for opens ABOVE the text, which is what
|
|
3184
|
+
* makes "at least 24pt" push a heading down its own line.
|
|
3185
|
+
*
|
|
3186
|
+
* `auto` — Word's "Multiple", the 1.15 every modern template sets — scales the
|
|
3187
|
+
* line instead, and the space it gains falls BELOW: the first line of a spaced
|
|
3188
|
+
* paragraph starts exactly where a single-spaced one does. Read the other way
|
|
3189
|
+
* round, fdo80800.docx's 1.5-line cells sat 6.6pt under the plain cell beside
|
|
3190
|
+
* them, where Word and LibreOffice write all three on one baseline.
|
|
3191
|
+
*
|
|
3192
|
+
* §17.6.5 — the extra a document GRID adds is not the paragraph's at all, and
|
|
3193
|
+
* is split evenly: the line is centred on its grid row.
|
|
3194
|
+
*
|
|
3195
|
+
* @param line The laid-out line.
|
|
3196
|
+
* @param p The owning paragraph's resolved properties.
|
|
3197
|
+
* @returns The distance from the line box's bottom edge up to the baseline.
|
|
3198
|
+
*/
|
|
3199
|
+
function lineBaselineOffset(line, p) {
|
|
3200
|
+
const descent = lineDescent(line);
|
|
3201
|
+
const natural = naturalLineHeight(line, p);
|
|
3202
|
+
const singled = naturalLineHeight(line, {
|
|
3203
|
+
...p,
|
|
3204
|
+
spacingLineRule: "auto",
|
|
3205
|
+
spacingLine: pt(0)
|
|
3206
|
+
});
|
|
3207
|
+
return descent + (p.spacingLineRule === "auto" ? Math.max(0, natural - singled) : 0) + (computeLineHeight(line, p) - natural) / 2;
|
|
3208
|
+
}
|
|
1713
3209
|
function alignmentOffset(alignment, lineWidth, available) {
|
|
1714
3210
|
if (alignment === "right") return Math.max(0, available - lineWidth);
|
|
1715
3211
|
if (alignment === "center") return Math.max(0, (available - lineWidth) / 2);
|
|
@@ -1724,7 +3220,7 @@ function layoutTableBlock(table, options, fontResources, imageResources, content
|
|
|
1724
3220
|
for (let r = 0; r < table.rows.length; r++) {
|
|
1725
3221
|
const rl = layoutTableRow(table.rows[r], r, table.rows.length, colCount, table.properties, columnWidthsPt, options, fontResources, imageResources, mergeRoles[r] ?? [], aboveBordersByCol);
|
|
1726
3222
|
const nextAbove = [];
|
|
1727
|
-
let ci = 0;
|
|
3223
|
+
let ci = table.rows[r].properties.gridBefore ?? 0;
|
|
1728
3224
|
for (const cell of table.rows[r].cells) {
|
|
1729
3225
|
const span = Math.max(1, cell.properties.colSpan ?? 1);
|
|
1730
3226
|
for (let k = 0; k < span; k++) nextAbove[ci + k] = cell.properties.borders;
|
|
@@ -1738,17 +3234,46 @@ function layoutTableBlock(table, options, fontResources, imageResources, content
|
|
|
1738
3234
|
...rp.pageBreakBefore ? { breakBefore: true } : {}
|
|
1739
3235
|
} : rl);
|
|
1740
3236
|
}
|
|
3237
|
+
growRowsForMerges(rows);
|
|
1741
3238
|
const heightPt = rows.reduce((s, r) => s + r.heightPt, 0);
|
|
1742
3239
|
const totalWidthPt = columnWidthsPt.reduce((s, w) => s + w, 0);
|
|
3240
|
+
const xOffsetPt = (table.properties.indentPt ?? 0) + tableXOffset(table.properties.alignment, contentWidth, totalWidthPt);
|
|
1743
3241
|
return {
|
|
1744
3242
|
kind: "table",
|
|
3243
|
+
...table.properties.float ? { float: table.properties.float } : {},
|
|
1745
3244
|
rows,
|
|
1746
3245
|
heightPt,
|
|
1747
3246
|
totalWidthPt,
|
|
1748
3247
|
colCount,
|
|
1749
|
-
xOffsetPt
|
|
3248
|
+
xOffsetPt
|
|
1750
3249
|
};
|
|
1751
3250
|
}
|
|
3251
|
+
/**
|
|
3252
|
+
* §17.4.85 `w:vMerge` — make every vertical merge fit. Each row is already as
|
|
3253
|
+
* tall as its own (unmerged) cells; a merge that needs more than its rows add
|
|
3254
|
+
* up to grows the LAST row of the run, which is where Word puts the slack.
|
|
3255
|
+
*
|
|
3256
|
+
* @param rows The table's laid-out rows, adjusted in place.
|
|
3257
|
+
*/
|
|
3258
|
+
function growRowsForMerges(rows) {
|
|
3259
|
+
for (let r = 0; r < rows.length; r++) for (const cell of rows[r].cells) {
|
|
3260
|
+
if (cell.mergeRole !== "start") continue;
|
|
3261
|
+
let last = r;
|
|
3262
|
+
for (let k = r + 1; k < rows.length; k++) {
|
|
3263
|
+
const below = rows[k].cells.find((c) => c.colStart === cell.colStart);
|
|
3264
|
+
if (!below || below.mergeRole !== "middle" && below.mergeRole !== "end") break;
|
|
3265
|
+
last = k;
|
|
3266
|
+
if (below.mergeRole === "end") break;
|
|
3267
|
+
}
|
|
3268
|
+
let have = 0;
|
|
3269
|
+
for (let k = r; k <= last; k++) have += rows[k].heightPt;
|
|
3270
|
+
const deficit = cell.totalHeightPt - have;
|
|
3271
|
+
if (deficit > .01) rows[last] = {
|
|
3272
|
+
...rows[last],
|
|
3273
|
+
heightPt: rows[last].heightPt + deficit
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
1752
3277
|
function tableXOffset(alignment, contentWidth, totalWidthPt) {
|
|
1753
3278
|
const slack = contentWidth - totalWidthPt;
|
|
1754
3279
|
if (slack <= 0 || !alignment || alignment === "left") return 0;
|
|
@@ -1757,15 +3282,18 @@ function tableXOffset(alignment, contentWidth, totalWidthPt) {
|
|
|
1757
3282
|
function computeColumnWidths(table, options, fontResources, contentWidth) {
|
|
1758
3283
|
let colCount = table.grid.length;
|
|
1759
3284
|
for (const row of table.rows) {
|
|
1760
|
-
let rowCols = 0;
|
|
3285
|
+
let rowCols = row.properties.gridBefore ?? 0;
|
|
1761
3286
|
for (const cell of row.cells) rowCols += Math.max(1, cell.properties.colSpan ?? 1);
|
|
1762
3287
|
if (rowCols > colCount) colCount = rowCols;
|
|
1763
3288
|
}
|
|
1764
3289
|
if (colCount === 0) return [];
|
|
1765
|
-
|
|
3290
|
+
const gridShort = gridColTwips(table).length < colCount;
|
|
3291
|
+
const declared = declaredColumnWidths(table, colCount, contentWidth, gridShort);
|
|
3292
|
+
if (declared) return declared;
|
|
3293
|
+
if (gridColTwips(table).some((w) => w > 0)) return gridWidthsScaled(table, contentWidth, colCount);
|
|
1766
3294
|
const colNaturalWidths = new Array(colCount).fill(0);
|
|
1767
3295
|
for (const row of table.rows) {
|
|
1768
|
-
let colIdx = 0;
|
|
3296
|
+
let colIdx = row.properties.gridBefore ?? 0;
|
|
1769
3297
|
for (let i = 0; i < row.cells.length; i++) {
|
|
1770
3298
|
const cell = row.cells[i];
|
|
1771
3299
|
const span = Math.max(1, cell.properties.colSpan ?? 1);
|
|
@@ -1798,6 +3326,81 @@ function computeColumnWidths(table, options, fontResources, contentWidth) {
|
|
|
1798
3326
|
function gridColTwips(table) {
|
|
1799
3327
|
return table.grid.map((w) => Math.round(w * 20));
|
|
1800
3328
|
}
|
|
3329
|
+
/**
|
|
3330
|
+
* §17.4.53 — column widths taken from the cells' own `w:tcW` for a table laid
|
|
3331
|
+
* out `fixed`. Each cell shares its preferred width over the columns it spans
|
|
3332
|
+
* and the widest claim wins; a column nobody declares keeps its grid share.
|
|
3333
|
+
* Returns `undefined` (⇒ the grid decides) unless the table says `fixed` and
|
|
3334
|
+
* enough of it is declared to trust.
|
|
3335
|
+
*/
|
|
3336
|
+
function declaredColumnWidths(table, colCount, contentWidth, gridShort = false) {
|
|
3337
|
+
if (table.properties.layout !== "fixed" && !gridShort) return void 0;
|
|
3338
|
+
const target = totalTableTarget(table, contentWidth);
|
|
3339
|
+
const declaredWidth = (cell) => {
|
|
3340
|
+
const p = cell.properties;
|
|
3341
|
+
if (p.widthFraction !== void 0) return p.widthFraction * target;
|
|
3342
|
+
if (p.width !== void 0 && p.widthType !== "auto" && p.widthType !== "nil") return p.width;
|
|
3343
|
+
};
|
|
3344
|
+
const claimed = new Array(colCount).fill(0);
|
|
3345
|
+
const spanning = [];
|
|
3346
|
+
let anyDeclared = false;
|
|
3347
|
+
let reach = 0;
|
|
3348
|
+
for (const row of table.rows) {
|
|
3349
|
+
if (row.cells.length < 2) continue;
|
|
3350
|
+
let span = row.properties.gridBefore ?? 0;
|
|
3351
|
+
for (const cell of row.cells) span += Math.max(1, cell.properties.colSpan ?? 1);
|
|
3352
|
+
if (span > reach) reach = span;
|
|
3353
|
+
}
|
|
3354
|
+
const phantomFrom = reach > 0 && reach < colCount ? reach : colCount;
|
|
3355
|
+
for (const row of table.rows) {
|
|
3356
|
+
let colIdx = row.properties.gridBefore ?? 0;
|
|
3357
|
+
for (const cell of row.cells) {
|
|
3358
|
+
const span = Math.max(1, cell.properties.colSpan ?? 1);
|
|
3359
|
+
const widthPt = declaredWidth(cell);
|
|
3360
|
+
if (widthPt !== void 0 && widthPt > 0) {
|
|
3361
|
+
anyDeclared = true;
|
|
3362
|
+
if (span === 1) {
|
|
3363
|
+
if (widthPt > claimed[colIdx]) claimed[colIdx] = widthPt;
|
|
3364
|
+
} else spanning.push({
|
|
3365
|
+
start: colIdx,
|
|
3366
|
+
span,
|
|
3367
|
+
widthPt
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
colIdx += span;
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
if (!anyDeclared) return void 0;
|
|
3374
|
+
for (const s of spanning) {
|
|
3375
|
+
const end = Math.min(s.start + s.span, phantomFrom);
|
|
3376
|
+
let have = 0;
|
|
3377
|
+
const blank = [];
|
|
3378
|
+
for (let i = s.start; i < end; i++) {
|
|
3379
|
+
have += claimed[i];
|
|
3380
|
+
if (claimed[i] === 0) blank.push(i);
|
|
3381
|
+
}
|
|
3382
|
+
const deficit = s.widthPt - have;
|
|
3383
|
+
if (deficit <= 0) continue;
|
|
3384
|
+
if (blank.length > 0) {
|
|
3385
|
+
const each = deficit / blank.length;
|
|
3386
|
+
for (const i of blank) claimed[i] = each;
|
|
3387
|
+
} else if (have > 0) for (let i = s.start; i < end; i++) claimed[i] = claimed[i] * (s.widthPt / have);
|
|
3388
|
+
}
|
|
3389
|
+
const gridTwips = gridColTwips(table);
|
|
3390
|
+
const rest = [];
|
|
3391
|
+
let claimedTotal = 0;
|
|
3392
|
+
for (let i = 0; i < phantomFrom; i++) if (claimed[i] > 0) claimedTotal += claimed[i];
|
|
3393
|
+
else rest.push(i);
|
|
3394
|
+
if (rest.length > 0) {
|
|
3395
|
+
const leftover = Math.max(0, target - claimedTotal);
|
|
3396
|
+
const restGrid = rest.reduce((s, i) => s + (gridTwips[i] ?? 0), 0);
|
|
3397
|
+
for (const i of rest) claimed[i] = restGrid > 0 ? (gridTwips[i] ?? 0) / restGrid * leftover : leftover / rest.length;
|
|
3398
|
+
}
|
|
3399
|
+
const total = claimed.reduce((s, w) => s + w, 0);
|
|
3400
|
+
if (total <= 0) return void 0;
|
|
3401
|
+
const scale = total > target ? target / total : 1;
|
|
3402
|
+
return claimed.map((w) => w * scale);
|
|
3403
|
+
}
|
|
1801
3404
|
function gridWidthsScaled(table, contentWidth, colCount) {
|
|
1802
3405
|
const gridTwips = gridColTwips(table);
|
|
1803
3406
|
const totalGridTwips = gridTwips.reduce((s, w) => s + w, 0);
|
|
@@ -1860,8 +3463,9 @@ function aboveOfSpan(aboveBordersByCol, colIdx, span) {
|
|
|
1860
3463
|
function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidthsPt, options, fontResources, imageResources, rowMergeRoles, aboveBordersByCol) {
|
|
1861
3464
|
const cells = [];
|
|
1862
3465
|
const columnXOffsets = [];
|
|
1863
|
-
|
|
1864
|
-
let
|
|
3466
|
+
const skip = Math.min(Math.max(0, row.properties.gridBefore ?? 0), columnWidthsPt.length);
|
|
3467
|
+
let cursorX = columnWidthsPt.slice(0, skip).reduce((sum, w) => sum + w, 0);
|
|
3468
|
+
let colIdx = skip;
|
|
1865
3469
|
for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
|
|
1866
3470
|
const cell = row.cells[cellIdx];
|
|
1867
3471
|
const span = Math.max(1, cell.properties.colSpan ?? 1);
|
|
@@ -1882,7 +3486,10 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
|
|
|
1882
3486
|
colIdx += span;
|
|
1883
3487
|
}
|
|
1884
3488
|
let heightPt = 0;
|
|
1885
|
-
for (const c of cells)
|
|
3489
|
+
for (const c of cells) {
|
|
3490
|
+
if (c.mergeRole === "start") continue;
|
|
3491
|
+
if (c.totalHeightPt > heightPt) heightPt = c.totalHeightPt;
|
|
3492
|
+
}
|
|
1886
3493
|
if (row.properties.height && row.properties.heightRule === "exact") {
|
|
1887
3494
|
heightPt = row.properties.height;
|
|
1888
3495
|
return {
|
|
@@ -1890,7 +3497,8 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
|
|
|
1890
3497
|
cells: cells.map((c) => clipCellToHeight(c, heightPt)),
|
|
1891
3498
|
columnXOffsets,
|
|
1892
3499
|
rowIdx,
|
|
1893
|
-
rowCount
|
|
3500
|
+
rowCount,
|
|
3501
|
+
...row.properties.cantSplit ? { cantSplit: true } : {}
|
|
1894
3502
|
};
|
|
1895
3503
|
} else if (row.properties.height && row.properties.heightRule === "atLeast") heightPt = Math.max(heightPt, row.properties.height);
|
|
1896
3504
|
return {
|
|
@@ -1898,7 +3506,8 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
|
|
|
1898
3506
|
cells,
|
|
1899
3507
|
columnXOffsets,
|
|
1900
3508
|
rowIdx,
|
|
1901
|
-
rowCount
|
|
3509
|
+
rowCount,
|
|
3510
|
+
...row.properties.cantSplit ? { cantSplit: true } : {}
|
|
1902
3511
|
};
|
|
1903
3512
|
}
|
|
1904
3513
|
/**
|
|
@@ -1930,6 +3539,31 @@ function clipCellToHeight(cell, heightPt) {
|
|
|
1930
3539
|
contentHeightPt: used
|
|
1931
3540
|
};
|
|
1932
3541
|
}
|
|
3542
|
+
/**
|
|
3543
|
+
* The one run a picture BLOCK is made of, so a context that lays out paragraphs
|
|
3544
|
+
* (a table cell) can draw it without a block path of its own.
|
|
3545
|
+
*
|
|
3546
|
+
* @param image The image block.
|
|
3547
|
+
* @returns A run carrying it as an inline picture.
|
|
3548
|
+
*/
|
|
3549
|
+
function imageRun(image) {
|
|
3550
|
+
return {
|
|
3551
|
+
text: "",
|
|
3552
|
+
properties: {},
|
|
3553
|
+
inlineImage: {
|
|
3554
|
+
...image.resource ? { resource: image.resource } : {},
|
|
3555
|
+
...image.outline ? { outline: image.outline } : {},
|
|
3556
|
+
...image.wash ? { wash: image.wash } : {},
|
|
3557
|
+
width: image.width,
|
|
3558
|
+
height: image.height,
|
|
3559
|
+
...image.crop ? { crop: image.crop } : {},
|
|
3560
|
+
...image.rotation60k ? { rotation60k: image.rotation60k } : {},
|
|
3561
|
+
...image.flipH ? { flipH: true } : {},
|
|
3562
|
+
...image.flipV ? { flipV: true } : {},
|
|
3563
|
+
...image.effectExtent ? { effectExtent: image.effectExtent } : {}
|
|
3564
|
+
}
|
|
3565
|
+
};
|
|
3566
|
+
}
|
|
1933
3567
|
function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imageResources, rowIdx, colStart, colSpan, rowCount, colCount, mergeRole, leftNeighborBorders, aboveNeighborBorders) {
|
|
1934
3568
|
const cellMar = cell.properties.margins;
|
|
1935
3569
|
const tableMar = tableProps.defaultCellMargins;
|
|
@@ -1942,8 +3576,19 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
1942
3576
|
const innerWidth = Math.max(1, widthPt - padLeftPt - padRightPt);
|
|
1943
3577
|
const lines = [];
|
|
1944
3578
|
const nestedTables = [];
|
|
3579
|
+
const cellShapes = [];
|
|
3580
|
+
const cellFloats = [];
|
|
3581
|
+
const lineGaps = /* @__PURE__ */ new Map();
|
|
1945
3582
|
let contentHeightPt = 0;
|
|
1946
3583
|
let clipped = false;
|
|
3584
|
+
let pendingGapPt = 0;
|
|
3585
|
+
const openParagraph = (spacingBeforePt) => {
|
|
3586
|
+
const gap = pendingGapPt + spacingBeforePt;
|
|
3587
|
+
pendingGapPt = 0;
|
|
3588
|
+
if (gap <= 0) return;
|
|
3589
|
+
lineGaps.set(lines.length, (lineGaps.get(lines.length) ?? 0) + gap);
|
|
3590
|
+
contentHeightPt += gap;
|
|
3591
|
+
};
|
|
1947
3592
|
if (mergeRole !== "middle" && mergeRole !== "end") {
|
|
1948
3593
|
for (const el of cell.content) if (el.kind === "paragraph") {
|
|
1949
3594
|
const noWrap = cell.properties.noWrap === true;
|
|
@@ -1951,16 +3596,50 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
1951
3596
|
const cutAt = widthPt - padLeftPt;
|
|
1952
3597
|
if (noWrap && (block.lines[0]?.contentWidthPt ?? 0) > cutAt) clipped = true;
|
|
1953
3598
|
const keep = noWrap ? clipLineToWidth(block.lines, cutAt, cell.properties.hashOnOverflow === true) : block.lines;
|
|
3599
|
+
const markOnly = cell.properties.hideMark === true && el.paragraph.runs.every((r) => r.text === "");
|
|
3600
|
+
if (!markOnly) openParagraph(block.spacingBeforePt);
|
|
1954
3601
|
for (const line of keep) {
|
|
1955
3602
|
lines.push(line);
|
|
1956
|
-
contentHeightPt += computeLineHeight(line, block.resolved);
|
|
3603
|
+
if (!markOnly) contentHeightPt += computeLineHeight(line, block.resolved);
|
|
1957
3604
|
}
|
|
1958
|
-
|
|
3605
|
+
if (!markOnly) pendingGapPt = block.spacingAfterPt;
|
|
1959
3606
|
} else if (el.kind === "table") {
|
|
1960
3607
|
const nested = layoutTableBlock(el.table, options, fontResources, imageResources, innerWidth);
|
|
1961
|
-
nestedTables.push(
|
|
3608
|
+
nestedTables.push({
|
|
3609
|
+
block: nested,
|
|
3610
|
+
afterLine: lines.length
|
|
3611
|
+
});
|
|
1962
3612
|
contentHeightPt += nested.heightPt;
|
|
1963
|
-
}
|
|
3613
|
+
} else if (el.kind === "image" && !isOutOfFlowFloat(el.image.float)) {
|
|
3614
|
+
const block = layoutParagraphBlock({
|
|
3615
|
+
properties: el.image.paragraphProperties,
|
|
3616
|
+
runs: [imageRun(el.image)]
|
|
3617
|
+
}, options, fontResources, imageResources, innerWidth);
|
|
3618
|
+
openParagraph(block.spacingBeforePt);
|
|
3619
|
+
for (const line of block.lines) {
|
|
3620
|
+
lines.push(line);
|
|
3621
|
+
contentHeightPt += computeLineHeight(line, block.resolved);
|
|
3622
|
+
}
|
|
3623
|
+
contentHeightPt += block.spacingAfterPt;
|
|
3624
|
+
pendingGapPt = block.spacingAfterPt;
|
|
3625
|
+
} else if (el.kind === "shape" && !isOutOfFlowFloat(el.shape.float)) {
|
|
3626
|
+
const block = layoutShapeBlock(el.shape, options, fontResources, imageResources, innerWidth);
|
|
3627
|
+
openParagraph(block.spacingBeforePt);
|
|
3628
|
+
cellShapes.push({
|
|
3629
|
+
block,
|
|
3630
|
+
afterLine: lines.length
|
|
3631
|
+
});
|
|
3632
|
+
contentHeightPt += block.heightPt;
|
|
3633
|
+
pendingGapPt = block.spacingAfterPt;
|
|
3634
|
+
} else if (el.kind === "image" && isOutOfFlowFloat(el.image.float)) cellFloats.push({
|
|
3635
|
+
block: layoutImageBlock(el.image, imageResources, innerWidth),
|
|
3636
|
+
afterLine: lines.length
|
|
3637
|
+
});
|
|
3638
|
+
else if (el.kind === "shape" && isOutOfFlowFloat(el.shape.float)) cellFloats.push({
|
|
3639
|
+
block: layoutShapeBlock(el.shape, options, fontResources, imageResources, innerWidth),
|
|
3640
|
+
afterLine: lines.length
|
|
3641
|
+
});
|
|
3642
|
+
contentHeightPt += pendingGapPt;
|
|
1964
3643
|
}
|
|
1965
3644
|
const colEnd = colStart + colSpan - 1;
|
|
1966
3645
|
const borders = resolveCellBorders(cell.properties.borders, tableProps.borders, rowIdx, colStart, colEnd, rowCount, colCount, mergeRole, leftNeighborBorders, aboveNeighborBorders);
|
|
@@ -1977,7 +3656,10 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
1977
3656
|
...cell.properties.icon ? { icon: cell.properties.icon } : {},
|
|
1978
3657
|
...cell.properties.sparkline ? { sparkline: cell.properties.sparkline } : {},
|
|
1979
3658
|
lines,
|
|
3659
|
+
...lineGaps.size > 0 ? { lineGaps } : {},
|
|
1980
3660
|
...nestedTables.length > 0 ? { nestedTables } : {},
|
|
3661
|
+
...cellShapes.length > 0 ? { shapes: cellShapes } : {},
|
|
3662
|
+
...cellFloats.length > 0 ? { floats: cellFloats } : {},
|
|
1981
3663
|
contentHeightPt,
|
|
1982
3664
|
totalHeightPt,
|
|
1983
3665
|
...cell.properties.verticalAlign ? { verticalAlign: cell.properties.verticalAlign } : {},
|
|
@@ -1992,6 +3674,7 @@ var BORDER_STYLE_RANK = {
|
|
|
1992
3674
|
thick: 4,
|
|
1993
3675
|
single: 3,
|
|
1994
3676
|
dashed: 2,
|
|
3677
|
+
dashSmallGap: 2,
|
|
1995
3678
|
dashDot: 2,
|
|
1996
3679
|
dashDotDot: 2,
|
|
1997
3680
|
dotted: 1,
|
|
@@ -2099,7 +3782,7 @@ function listItemParagraphNode(builder, stack, list, wantLbl) {
|
|
|
2099
3782
|
* Push order into `current`/`pages` IS the byte order of the emitted PDF, so the
|
|
2100
3783
|
* bodies move unchanged.
|
|
2101
3784
|
*/
|
|
2102
|
-
var PageAssembler = class {
|
|
3785
|
+
var PageAssembler = class PageAssembler {
|
|
2103
3786
|
/**
|
|
2104
3787
|
* @param sectionCtxs The per-section render contexts, in order; assembly
|
|
2105
3788
|
* starts on the first section's first page.
|
|
@@ -2109,20 +3792,29 @@ var PageAssembler = class {
|
|
|
2109
3792
|
* `undefined` when there are none.
|
|
2110
3793
|
* @param bookmarkPositions Out-param: bookmark name → its GoTo destination.
|
|
2111
3794
|
*/
|
|
2112
|
-
constructor(sectionCtxs, builder, notes, bookmarkPositions) {
|
|
3795
|
+
constructor(sectionCtxs, builder, notes, bookmarkPositions, backgroundImageName) {
|
|
2113
3796
|
this.sectionCtxs = sectionCtxs;
|
|
2114
3797
|
this.builder = builder;
|
|
2115
3798
|
this.notes = notes;
|
|
2116
3799
|
this.bookmarkPositions = bookmarkPositions;
|
|
3800
|
+
this.backgroundImageName = backgroundImageName;
|
|
2117
3801
|
this.ctx = sectionCtxs[0];
|
|
3802
|
+
this.pageStartCtx = this.ctx;
|
|
3803
|
+
this.pageNumber = this.ctx.properties.pageNumberStart ?? 1;
|
|
2118
3804
|
this.cursorY = this.ctx.pageHeight - this.ctx.marginTop;
|
|
2119
3805
|
this.colStartY = this.cursorY;
|
|
3806
|
+
this.bandTopY = this.cursorY;
|
|
2120
3807
|
}
|
|
2121
3808
|
pages = [];
|
|
2122
3809
|
ctx;
|
|
2123
3810
|
secIdx = 0;
|
|
2124
3811
|
pageInSection = 0;
|
|
2125
3812
|
globalPageIdx = 0;
|
|
3813
|
+
/**
|
|
3814
|
+
* §17.6.12 — the number the NEXT page prints for a PAGE field. Counts up with
|
|
3815
|
+
* the pages and restarts wherever a section names a `w:pgNumType w:start`.
|
|
3816
|
+
*/
|
|
3817
|
+
pageNumber = 1;
|
|
2126
3818
|
current = [];
|
|
2127
3819
|
pendingPageBreak = false;
|
|
2128
3820
|
cursorY;
|
|
@@ -2136,11 +3828,23 @@ var PageAssembler = class {
|
|
|
2136
3828
|
colStartLen = 0;
|
|
2137
3829
|
/** The cursor's y at the top of the current column — see {@link colHasContent}. */
|
|
2138
3830
|
colStartY;
|
|
3831
|
+
/**
|
|
3832
|
+
* Where this page's column band begins. The top margin on an ordinary page;
|
|
3833
|
+
* further down when a §17.6.22 `continuous` section started mid-page, whose
|
|
3834
|
+
* columns run beside each other from there, not from the paper's top.
|
|
3835
|
+
*/
|
|
3836
|
+
bandTopY;
|
|
2139
3837
|
/** The current column's left edge (`marginLeft` plus the column x-offset). */
|
|
2140
3838
|
colLeft = () => this.ctx.marginLeft + (this.ctx.columns?.[this.colIdx]?.xOffsetPt ?? 0);
|
|
2141
3839
|
/** The current column's width (the section content width when single-column). */
|
|
2142
3840
|
colWidth = () => this.ctx.columns?.[this.colIdx]?.widthPt ?? this.ctx.contentWidth;
|
|
2143
3841
|
/**
|
|
3842
|
+
* The measure the section's blocks were BROKEN at — the first column's, since
|
|
3843
|
+
* that is all the block pass can know. A column of another width has to
|
|
3844
|
+
* re-break what lands in it.
|
|
3845
|
+
*/
|
|
3846
|
+
builtWidth = () => this.ctx.columns?.[0]?.widthPt ?? this.ctx.contentWidth;
|
|
3847
|
+
/**
|
|
2144
3848
|
* Whether the current column is already in use.
|
|
2145
3849
|
*
|
|
2146
3850
|
* This gates every overflow break, so that a block too tall for an empty page
|
|
@@ -2167,15 +3871,118 @@ var PageAssembler = class {
|
|
|
2167
3871
|
if (this.ctx.columns && this.colIdx + 1 < this.ctx.columns.length) {
|
|
2168
3872
|
this.colIdx++;
|
|
2169
3873
|
this.colStartLen = this.current.length;
|
|
2170
|
-
this.cursorY = this.
|
|
3874
|
+
this.cursorY = this.bandTopY;
|
|
2171
3875
|
this.colStartY = this.cursorY;
|
|
3876
|
+
const isLast = this.colIdx + 1 >= this.ctx.columns.length;
|
|
3877
|
+
this.balanceBottomY = this.balanceHeightPt > 0 && !isLast ? this.cursorY - this.balanceHeightPt : void 0;
|
|
2172
3878
|
} else this.flushPage();
|
|
2173
3879
|
};
|
|
2174
3880
|
/**
|
|
3881
|
+
* §17.6.8 — the running line count for the margin numbers. Reset by the
|
|
3882
|
+
* section's own `w:restart` rule.
|
|
3883
|
+
*/
|
|
3884
|
+
lineNumber = 0;
|
|
3885
|
+
/**
|
|
3886
|
+
* §17.6.8 — the margin number for the body line just placed, if the section
|
|
3887
|
+
* asks for one at this count. Pushed at the line's own baseline, right-
|
|
3888
|
+
* aligned a quarter inch off the text.
|
|
3889
|
+
*
|
|
3890
|
+
* @param baselineY The line's baseline, in page (y-down) coordinates.
|
|
3891
|
+
* @param line The line being numbered — its font draws the digits.
|
|
3892
|
+
*/
|
|
3893
|
+
emitLineNumber = (baselineY, line) => {
|
|
3894
|
+
const ln = this.ctx.properties.lineNumbering;
|
|
3895
|
+
if (!ln) return;
|
|
3896
|
+
this.lineNumber++;
|
|
3897
|
+
const n = ln.start + this.lineNumber - 1;
|
|
3898
|
+
if ((n - ln.start + 1) % ln.countBy !== 0) return;
|
|
3899
|
+
const token = line.tokens.find((t) => t.kind === "text");
|
|
3900
|
+
if (!token) return;
|
|
3901
|
+
const text = String(n);
|
|
3902
|
+
const sizePt = token.fontSizePt;
|
|
3903
|
+
const widthPt = token.font.measure.textWidthPt(text, sizePt);
|
|
3904
|
+
const digits = {
|
|
3905
|
+
kind: "text",
|
|
3906
|
+
text,
|
|
3907
|
+
isSpace: false,
|
|
3908
|
+
resolvedRun: token.resolvedRun,
|
|
3909
|
+
font: token.font,
|
|
3910
|
+
fontSizePt: sizePt,
|
|
3911
|
+
widthPt,
|
|
3912
|
+
bidiLevel: 0
|
|
3913
|
+
};
|
|
3914
|
+
const numbered = {
|
|
3915
|
+
...line,
|
|
3916
|
+
tokens: [digits],
|
|
3917
|
+
contentWidthPt: widthPt
|
|
3918
|
+
};
|
|
3919
|
+
this.current.push({
|
|
3920
|
+
type: "line",
|
|
3921
|
+
line: numbered,
|
|
3922
|
+
originX: pt(this.colLeft() - (ln.distancePt ?? LINE_NUMBER_GAP_PT) - widthPt),
|
|
3923
|
+
baselineY: pt(baselineY)
|
|
3924
|
+
});
|
|
3925
|
+
};
|
|
3926
|
+
/**
|
|
3927
|
+
* §17.6.12 — a section that names its own first page number restarts the
|
|
3928
|
+
* count there; one that names none carries on from the section before.
|
|
3929
|
+
*
|
|
3930
|
+
* @param next The section being entered.
|
|
3931
|
+
*/
|
|
3932
|
+
restartPageNumbers = (next) => {
|
|
3933
|
+
const start = next.properties.pageNumberStart;
|
|
3934
|
+
if (start !== void 0) this.pageNumber = start;
|
|
3935
|
+
if ((next.properties.lineNumbering?.restart ?? "newPage") !== "continuous") this.lineNumber = 0;
|
|
3936
|
+
};
|
|
3937
|
+
/**
|
|
3938
|
+
* §17.6.22 — start a `continuous` section on the page in hand: its columns
|
|
3939
|
+
* begin where the section does, not at the top of the paper.
|
|
3940
|
+
*
|
|
3941
|
+
* @param next The section to continue into.
|
|
3942
|
+
*/
|
|
3943
|
+
startBandSection = (next, bandHeightPt) => {
|
|
3944
|
+
this.secIdx++;
|
|
3945
|
+
this.ctx = next;
|
|
3946
|
+
this.restartPageNumbers(next);
|
|
3947
|
+
this.colIdx = 0;
|
|
3948
|
+
this.colStartLen = this.current.length;
|
|
3949
|
+
this.cursorY = Math.min(this.cursorY, next.pageHeight - next.marginTop);
|
|
3950
|
+
this.bandTopY = this.cursorY;
|
|
3951
|
+
this.colStartY = this.cursorY;
|
|
3952
|
+
this.beginBalancedBand(bandHeightPt);
|
|
3953
|
+
};
|
|
3954
|
+
/**
|
|
3955
|
+
* §17.6.4 — even the columns of the band that starts at the cursor: each gets
|
|
3956
|
+
* the same share of `bandHeightPt`. A band too tall for the page fills column
|
|
3957
|
+
* after column as before; there is nothing to even out when it will spill
|
|
3958
|
+
* anyway.
|
|
3959
|
+
*
|
|
3960
|
+
* @param bandHeightPt The height of everything the band holds.
|
|
3961
|
+
*/
|
|
3962
|
+
beginBalancedBand = (bandHeightPt) => {
|
|
3963
|
+
const colCount = this.ctx.columns?.length ?? 1;
|
|
3964
|
+
const room = this.cursorY - (this.ctx.marginBottom + this.noteReserve);
|
|
3965
|
+
const share = colCount > 1 ? balancedColumnHeight(bandHeightPt, colCount, room) : 0;
|
|
3966
|
+
this.balanceHeightPt = share;
|
|
3967
|
+
this.balanceBottomY = share > 0 ? this.cursorY - share : void 0;
|
|
3968
|
+
};
|
|
3969
|
+
/**
|
|
2175
3970
|
* §20.4.2.3 out-of-flow drawings (wrap `'none'`): they render at their anchored
|
|
2176
3971
|
* position without moving the cursor. `behindDoc` sinks below the body text,
|
|
2177
3972
|
* everything else above it; both flush with the page.
|
|
2178
3973
|
*/
|
|
3974
|
+
/**
|
|
3975
|
+
* §20.4.2.3 — a float layer, drawn in `relativeHeight` order. A stable sort
|
|
3976
|
+
* by z leaves the ones that state none where the document put them:
|
|
3977
|
+
* dml-rectangle-relsize.docx writes its blue bar first and gives it the
|
|
3978
|
+
* higher z, and drawn in document order the white rectangle covered it.
|
|
3979
|
+
*/
|
|
3980
|
+
/** A float layer that stamps each item it takes with the float's z-order. */
|
|
3981
|
+
static zSink = (target, z) => z === void 0 ? target : { push: (...items) => target.push(...items.map((i) => ({
|
|
3982
|
+
...i,
|
|
3983
|
+
z
|
|
3984
|
+
}))) };
|
|
3985
|
+
static byZ = (items) => items.every((i) => i.z === void 0) ? [...items] : [...items].sort((a, b) => (a.z ?? 0) - (b.z ?? 0));
|
|
2179
3986
|
floatsBehind = [];
|
|
2180
3987
|
floatsFront = [];
|
|
2181
3988
|
/**
|
|
@@ -2183,27 +3990,42 @@ var PageAssembler = class {
|
|
|
2183
3990
|
* must flow around. Page-scoped, like the float graphics.
|
|
2184
3991
|
*/
|
|
2185
3992
|
exclusions = [];
|
|
2186
|
-
/**
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
const
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
3993
|
+
/**
|
|
3994
|
+
* Claim the area a side-wrapping float keeps to itself: its own box grown by
|
|
3995
|
+
* the §20.4.2.3 `distT/B/L/R` stand-off the anchor asks for.
|
|
3996
|
+
*/
|
|
3997
|
+
excludeFloat = (f, x, topYUp, w, h) => {
|
|
3998
|
+
const d = f.wrapDist;
|
|
3999
|
+
const wide = f.wrap === "notBeside";
|
|
4000
|
+
this.exclusions.push({
|
|
4001
|
+
x0: wide ? this.colLeft() : x - (d?.leftPt ?? 0),
|
|
4002
|
+
x1: wide ? this.colLeft() + this.colWidth() : x + w + (d?.rightPt ?? 0),
|
|
4003
|
+
topYUp: topYUp + (d?.topPt ?? 0),
|
|
4004
|
+
bottomYUp: topYUp - h - (d?.bottomPt ?? 0),
|
|
4005
|
+
...f.wrapSide === "bothSides" ? { wrapBothSides: true } : {}
|
|
4006
|
+
});
|
|
2195
4007
|
};
|
|
4008
|
+
/** The frame a float anchored in the body flow is resolved against. */
|
|
4009
|
+
floatFrame = () => ({
|
|
4010
|
+
pageLeft: 0,
|
|
4011
|
+
pageWidth: this.ctx.pageWidth,
|
|
4012
|
+
pageTopYUp: this.ctx.pageHeight,
|
|
4013
|
+
pageBottomYUp: 0,
|
|
4014
|
+
marginLeft: this.ctx.marginLeft,
|
|
4015
|
+
contentWidth: this.ctx.contentWidth,
|
|
4016
|
+
marginTopYUp: this.ctx.pageHeight - this.ctx.marginTop,
|
|
4017
|
+
marginBottomYUp: this.ctx.marginBottom,
|
|
4018
|
+
columnLeft: this.colLeft(),
|
|
4019
|
+
columnWidth: this.colWidth(),
|
|
4020
|
+
anchorY: this.cursorY
|
|
4021
|
+
});
|
|
4022
|
+
/** The float's left edge on the page, resolved from its horizontal anchor. */
|
|
4023
|
+
floatX = (f, widthPt) => floatXIn(this.floatFrame(), f, widthPt);
|
|
2196
4024
|
/**
|
|
2197
4025
|
* The drawing's TOP in the y-up cursor frame. Paragraph/line-relative offsets
|
|
2198
4026
|
* hang off the anchoring paragraph's current position.
|
|
2199
4027
|
*/
|
|
2200
|
-
floatTopYUp = (f) =>
|
|
2201
|
-
const v = f.posV;
|
|
2202
|
-
if (!v) return this.cursorY;
|
|
2203
|
-
if (v.relativeFrom === "page") return this.ctx.pageHeight - (v.offsetPt ?? 0);
|
|
2204
|
-
if (v.relativeFrom === "margin") return this.ctx.pageHeight - this.ctx.marginTop - (v.offsetPt ?? 0);
|
|
2205
|
-
return this.cursorY - (v.offsetPt ?? 0);
|
|
2206
|
-
};
|
|
4028
|
+
floatTopYUp = (f, heightPt = 0) => floatTopYUpIn(this.floatFrame(), f, heightPt);
|
|
2207
4029
|
/**
|
|
2208
4030
|
* Tagged PDF: the stack of open list levels (for L/LI/LBody nesting). Cleared
|
|
2209
4031
|
* whenever a non-list-item block interrupts the run of list paragraphs.
|
|
@@ -2219,7 +4041,21 @@ var PageAssembler = class {
|
|
|
2219
4041
|
noteReserve = 0;
|
|
2220
4042
|
placedNotes = /* @__PURE__ */ new Set();
|
|
2221
4043
|
/** The page's usable bottom: the margin plus whatever the notes band has claimed so far. */
|
|
2222
|
-
|
|
4044
|
+
/**
|
|
4045
|
+
* §17.6.4 — where the current column must stop. Normally the page's bottom
|
|
4046
|
+
* margin (plus any footnote reserve); inside a BALANCED band it is the line
|
|
4047
|
+
* the band's share of the height reaches, so the columns come out even.
|
|
4048
|
+
*/
|
|
4049
|
+
bottomLimit = () => Math.max(this.ctx.marginBottom + this.noteReserve, this.balanceBottomY ?? Number.NEGATIVE_INFINITY);
|
|
4050
|
+
/**
|
|
4051
|
+
* §17.6.4 — a `continuous` section of several columns whose content fits the
|
|
4052
|
+
* page is BALANCED: Word evens the columns out rather than filling the first
|
|
4053
|
+
* to the bottom. Set while such a band is being laid out; the height each
|
|
4054
|
+
* column is given is `balanceHeightPt`. IndexFieldFlagF.docx sets its index
|
|
4055
|
+
* that way and we filled column one and left the rest empty.
|
|
4056
|
+
*/
|
|
4057
|
+
balanceBottomY;
|
|
4058
|
+
balanceHeightPt = 0;
|
|
2223
4059
|
/**
|
|
2224
4060
|
* The first exclusion a line spanning `[yTop-h, yTop]` collides with in the
|
|
2225
4061
|
* current column (x-overlap with the column required).
|
|
@@ -2247,15 +4083,25 @@ var PageAssembler = class {
|
|
|
2247
4083
|
const cl = this.colLeft();
|
|
2248
4084
|
const leftRoom = e.x0 - FLOAT_TEXT_GAP - cl;
|
|
2249
4085
|
const rightRoom = cl + full - (e.x1 + FLOAT_TEXT_GAP);
|
|
2250
|
-
if (rightRoom
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
4086
|
+
if (Math.max(leftRoom, rightRoom) < MIN_WRAP_WIDTH) return {
|
|
4087
|
+
width: full,
|
|
4088
|
+
xOffset: 0,
|
|
4089
|
+
blockedUntilYUp: e.bottomYUp
|
|
4090
|
+
};
|
|
4091
|
+
if (leftRoom >= MIN_WRAP_WIDTH && rightRoom >= MIN_WRAP_WIDTH && e.wrapBothSides) return {
|
|
4092
|
+
width: leftRoom,
|
|
4093
|
+
xOffset: 0,
|
|
4094
|
+
pair: {
|
|
4095
|
+
width: rightRoom,
|
|
4096
|
+
xOffset: full - rightRoom
|
|
4097
|
+
}
|
|
4098
|
+
};
|
|
4099
|
+
if (rightRoom >= leftRoom) return {
|
|
4100
|
+
width: rightRoom,
|
|
4101
|
+
xOffset: full - rightRoom
|
|
4102
|
+
};
|
|
2257
4103
|
return {
|
|
2258
|
-
width:
|
|
4104
|
+
width: leftRoom,
|
|
2259
4105
|
xOffset: 0
|
|
2260
4106
|
};
|
|
2261
4107
|
};
|
|
@@ -2274,9 +4120,16 @@ var PageAssembler = class {
|
|
|
2274
4120
|
let y = startY;
|
|
2275
4121
|
for (let i = 0; i < 200; i++) {
|
|
2276
4122
|
const g = this.lineGeometryAt(y, h0);
|
|
4123
|
+
if (g.blockedUntilYUp !== void 0) {
|
|
4124
|
+
widths.push(this.colWidth());
|
|
4125
|
+
y = g.blockedUntilYUp - h0;
|
|
4126
|
+
if (y < this.bottomLimit()) break;
|
|
4127
|
+
continue;
|
|
4128
|
+
}
|
|
2277
4129
|
widths.push(g.width);
|
|
4130
|
+
if (g.pair) widths.push(g.pair.width);
|
|
2278
4131
|
if (g.width < this.colWidth()) narrowed = true;
|
|
2279
|
-
else if (
|
|
4132
|
+
else if (narrowed) break;
|
|
2280
4133
|
y -= h0;
|
|
2281
4134
|
if (y < this.bottomLimit()) break;
|
|
2282
4135
|
}
|
|
@@ -2328,7 +4181,7 @@ var PageAssembler = class {
|
|
|
2328
4181
|
const noteNode = this.builder.create("Note", this.builder.root);
|
|
2329
4182
|
structId = this.builder.create("P", noteNode).id;
|
|
2330
4183
|
}
|
|
2331
|
-
out.push(...drawBlocksSequentially(note.blocks, this.ctx.marginLeft, cursor, this.ctx.pageHeight, structId));
|
|
4184
|
+
out.push(...drawBlocksSequentially(note.blocks, this.ctx.marginLeft, cursor, this.ctx.pageHeight, this.ctx.contentWidth, structId));
|
|
2332
4185
|
cursor -= note.heightPt;
|
|
2333
4186
|
}
|
|
2334
4187
|
return out;
|
|
@@ -2353,22 +4206,25 @@ var PageAssembler = class {
|
|
|
2353
4206
|
const footer = pickBand(this.ctx.footerSet, band);
|
|
2354
4207
|
if (header.renderDynamic) this.dynBands.push({
|
|
2355
4208
|
pageIdx: this.pages.length,
|
|
2356
|
-
pageNumber: this.
|
|
4209
|
+
pageNumber: this.pageNumber,
|
|
2357
4210
|
position: "header",
|
|
2358
4211
|
render: header.renderDynamic
|
|
2359
4212
|
});
|
|
2360
4213
|
if (footer.renderDynamic) this.dynBands.push({
|
|
2361
4214
|
pageIdx: this.pages.length,
|
|
2362
|
-
pageNumber: this.
|
|
4215
|
+
pageNumber: this.pageNumber,
|
|
2363
4216
|
position: "footer",
|
|
2364
4217
|
render: footer.renderDynamic
|
|
2365
4218
|
});
|
|
2366
4219
|
this.pages.push({
|
|
2367
4220
|
commands: [
|
|
4221
|
+
...this.pageBackgroundItems(),
|
|
4222
|
+
...this.pageBorderItems(),
|
|
4223
|
+
...this.columnSeparatorItems(),
|
|
2368
4224
|
...header.commands,
|
|
2369
|
-
...this.floatsBehind,
|
|
4225
|
+
...PageAssembler.byZ(this.floatsBehind),
|
|
2370
4226
|
...this.current,
|
|
2371
|
-
...this.floatsFront,
|
|
4227
|
+
...PageAssembler.byZ(this.floatsFront),
|
|
2372
4228
|
...this.renderNotesBand(),
|
|
2373
4229
|
...footer.commands
|
|
2374
4230
|
],
|
|
@@ -2385,35 +4241,214 @@ var PageAssembler = class {
|
|
|
2385
4241
|
this.colStartLen = 0;
|
|
2386
4242
|
this.pageInSection++;
|
|
2387
4243
|
this.globalPageIdx++;
|
|
4244
|
+
this.pageNumber++;
|
|
4245
|
+
if ((this.ctx.properties.lineNumbering?.restart ?? "newPage") === "newPage") this.lineNumber = 0;
|
|
4246
|
+
this.cursorY = this.ctx.pageHeight - this.ctx.marginTop;
|
|
4247
|
+
this.colStartY = this.cursorY;
|
|
4248
|
+
this.bandTopY = this.cursorY;
|
|
4249
|
+
this.balanceBottomY = void 0;
|
|
4250
|
+
this.balanceHeightPt = 0;
|
|
4251
|
+
this.pageStartCtx = this.ctx;
|
|
4252
|
+
};
|
|
4253
|
+
/**
|
|
4254
|
+
* §17.6.4 `w:cols w:sep` — the rule down the middle of every gutter, run the
|
|
4255
|
+
* height of the text area. multi-column-separator-with-line.docx asks for one
|
|
4256
|
+
* and we drew its two columns with nothing between them.
|
|
4257
|
+
*
|
|
4258
|
+
* @returns One border item per gutter, or none when the section asks for no rule.
|
|
4259
|
+
*/
|
|
4260
|
+
columnSeparatorItems = () => {
|
|
4261
|
+
const ctx = this.pageStartCtx;
|
|
4262
|
+
const cols = ctx.columns;
|
|
4263
|
+
if (!cols || cols.length < 2 || ctx.properties.columns?.separator !== true) return [];
|
|
4264
|
+
let deepest = ctx.marginTop;
|
|
4265
|
+
for (const item of this.current) {
|
|
4266
|
+
const bottom = item.type === "line" ? item.baselineY : item.type === "shape" ? 0 : item.y + item.height;
|
|
4267
|
+
if (bottom > deepest) deepest = bottom;
|
|
4268
|
+
}
|
|
4269
|
+
const height = Math.min(deepest, ctx.pageHeight - ctx.marginBottom) - ctx.marginTop;
|
|
4270
|
+
if (height <= 0) return [];
|
|
4271
|
+
const out = [];
|
|
4272
|
+
for (let i = 1; i < cols.length; i++) {
|
|
4273
|
+
const left = cols[i - 1];
|
|
4274
|
+
const right = cols[i];
|
|
4275
|
+
const x = ctx.marginLeft + (left.xOffsetPt + left.widthPt + right.xOffsetPt) / 2;
|
|
4276
|
+
out.push({
|
|
4277
|
+
type: "border",
|
|
4278
|
+
side: "left",
|
|
4279
|
+
x: pt(x),
|
|
4280
|
+
y: pt(ctx.marginTop),
|
|
4281
|
+
width: pt(0),
|
|
4282
|
+
height: pt(height),
|
|
4283
|
+
borderSizePt: COLUMN_SEPARATOR_PT,
|
|
4284
|
+
borderColorHex: "000000"
|
|
4285
|
+
});
|
|
4286
|
+
}
|
|
4287
|
+
return out;
|
|
4288
|
+
};
|
|
4289
|
+
/** The section context the CURRENT page began in (see {@link columnSeparatorItems}). */
|
|
4290
|
+
pageStartCtx;
|
|
4291
|
+
/**
|
|
4292
|
+
* §17.6.10 `w:pgBorders` — the rules around the page. `@w:offsetFrom` says
|
|
4293
|
+
* what each edge's space is measured from: the paper's own edge, or the text
|
|
4294
|
+
* margin the rule stands outside of. a4andborders.docx frames every page and
|
|
4295
|
+
* we drew nothing.
|
|
4296
|
+
*
|
|
4297
|
+
* @returns The border items for this page, in paint order.
|
|
4298
|
+
*/
|
|
4299
|
+
/**
|
|
4300
|
+
* §17.2.1 — what the page is painted before anything is drawn on it: the
|
|
4301
|
+
* `v:background`'s PICTURE or GRADIENT when it has one, else the flat
|
|
4302
|
+
* `w:background @w:color`. Read as the colour alone, fill.docx's five-stop
|
|
4303
|
+
* sweep came out the navy of its fallback and tdf126533_pageBitmap.docx's
|
|
4304
|
+
* paper came out plain.
|
|
4305
|
+
*
|
|
4306
|
+
* @returns The background items for this page (at most one).
|
|
4307
|
+
*/
|
|
4308
|
+
pageBackgroundItems = () => {
|
|
4309
|
+
const options = this.ctx.options;
|
|
4310
|
+
const w = this.ctx.pageWidth;
|
|
4311
|
+
const h = this.ctx.pageHeight;
|
|
4312
|
+
const fill = options?.pageBackgroundFill;
|
|
4313
|
+
if (fill?.kind === "picture" && this.backgroundImageName !== void 0) return [{
|
|
4314
|
+
type: "image",
|
|
4315
|
+
x: pt(0),
|
|
4316
|
+
y: pt(0),
|
|
4317
|
+
width: pt(w),
|
|
4318
|
+
height: pt(h),
|
|
4319
|
+
imageResourceName: this.backgroundImageName
|
|
4320
|
+
}];
|
|
4321
|
+
if (fill?.kind === "gradient" && fill.gradient) return [{
|
|
4322
|
+
type: "shape",
|
|
4323
|
+
shape: {
|
|
4324
|
+
paths: [rectAtPath(0, 0, w, h)],
|
|
4325
|
+
fillColorHex: gradientToSolid(fill.gradient),
|
|
4326
|
+
fillGradient: fill.gradient,
|
|
4327
|
+
transform: flipTransform([
|
|
4328
|
+
1,
|
|
4329
|
+
0,
|
|
4330
|
+
0,
|
|
4331
|
+
1,
|
|
4332
|
+
0,
|
|
4333
|
+
0
|
|
4334
|
+
], h)
|
|
4335
|
+
}
|
|
4336
|
+
}];
|
|
4337
|
+
if (options?.pageBackgroundColorHex !== void 0) return [{
|
|
4338
|
+
type: "fill",
|
|
4339
|
+
x: pt(0),
|
|
4340
|
+
y: pt(0),
|
|
4341
|
+
width: pt(w),
|
|
4342
|
+
height: pt(h),
|
|
4343
|
+
fillColorHex: options.pageBackgroundColorHex
|
|
4344
|
+
}];
|
|
4345
|
+
return [];
|
|
4346
|
+
};
|
|
4347
|
+
pageBorderItems = () => {
|
|
4348
|
+
const pg = this.ctx.properties.pageBorders;
|
|
4349
|
+
if (!pg) return [];
|
|
4350
|
+
const H = this.ctx.pageHeight;
|
|
4351
|
+
const W = this.ctx.pageWidth;
|
|
4352
|
+
const out = [];
|
|
4353
|
+
for (const side of [
|
|
4354
|
+
"top",
|
|
4355
|
+
"bottom",
|
|
4356
|
+
"left",
|
|
4357
|
+
"right"
|
|
4358
|
+
]) {
|
|
4359
|
+
const edge = pg.borders[side];
|
|
4360
|
+
if (!edge || edge.style === "none") continue;
|
|
4361
|
+
const gap = edge.spacePt ?? 0;
|
|
4362
|
+
const x0 = pg.offsetFrom === "page" ? gap : this.ctx.marginLeft - gap;
|
|
4363
|
+
const x1 = pg.offsetFrom === "page" ? W - gap : W - this.ctx.marginLeft + gap;
|
|
4364
|
+
const yTop = pg.offsetFrom === "page" ? gap : this.ctx.marginTop - gap;
|
|
4365
|
+
const yBottom = pg.offsetFrom === "page" ? H - gap : H - this.ctx.marginBottom + gap;
|
|
4366
|
+
out.push({
|
|
4367
|
+
type: "border",
|
|
4368
|
+
side,
|
|
4369
|
+
x: pt(Math.max(0, x0)),
|
|
4370
|
+
y: pt(Math.max(0, yTop)),
|
|
4371
|
+
width: pt(Math.max(0, x1 - x0)),
|
|
4372
|
+
height: pt(Math.max(0, yBottom - yTop)),
|
|
4373
|
+
borderSizePt: edge.width ?? DEFAULT_BORDER_SIZE_EIGHTH * EIGHTH_PT,
|
|
4374
|
+
borderColorHex: edge.colorHex ?? "000000",
|
|
4375
|
+
...edge.style !== "single" ? { borderStyle: edge.style } : {}
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
return out;
|
|
4379
|
+
};
|
|
4380
|
+
/**
|
|
4381
|
+
* Throw the in-progress page away and reset for the next one.
|
|
4382
|
+
*
|
|
4383
|
+
* A section boundary reached with a page that draws nothing is the one place
|
|
4384
|
+
* this is wanted: {@link flushPage} keeps such a page whenever the cursor
|
|
4385
|
+
* moved at all, which is right in the middle of a flow and wrong at a
|
|
4386
|
+
* boundary, where the space consumed was the section break itself.
|
|
4387
|
+
*/
|
|
4388
|
+
dropPage = () => {
|
|
4389
|
+
this.current = [];
|
|
4390
|
+
this.floatsBehind = [];
|
|
4391
|
+
this.floatsFront = [];
|
|
4392
|
+
this.exclusions = [];
|
|
4393
|
+
this.pageNotes = [];
|
|
4394
|
+
this.noteReserve = 0;
|
|
4395
|
+
this.colIdx = 0;
|
|
4396
|
+
this.colStartLen = 0;
|
|
2388
4397
|
this.cursorY = this.ctx.pageHeight - this.ctx.marginTop;
|
|
2389
4398
|
this.colStartY = this.cursorY;
|
|
4399
|
+
this.bandTopY = this.cursorY;
|
|
4400
|
+
this.balanceBottomY = void 0;
|
|
4401
|
+
this.balanceHeightPt = 0;
|
|
2390
4402
|
};
|
|
2391
4403
|
};
|
|
2392
|
-
function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph) {
|
|
4404
|
+
function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph, backgroundImageName) {
|
|
2393
4405
|
if (sectionCtxs.length === 0) return [];
|
|
2394
|
-
const asm = new PageAssembler(sectionCtxs, builder, notes, bookmarkPositions);
|
|
4406
|
+
const asm = new PageAssembler(sectionCtxs, builder, notes, bookmarkPositions, backgroundImageName);
|
|
4407
|
+
const balanceIfEndsContinuous = (fromBlock) => {
|
|
4408
|
+
const here = asm.ctx;
|
|
4409
|
+
const next = sectionCtxs[asm.secIdx + 1];
|
|
4410
|
+
if ((here.columns?.length ?? 1) < 2) return;
|
|
4411
|
+
if (!next?.continuous) return;
|
|
4412
|
+
if (next.pageWidth !== here.pageWidth || next.pageHeight !== here.pageHeight) return;
|
|
4413
|
+
const end = Math.min(here.endIndex, blocks.length);
|
|
4414
|
+
asm.beginBalancedBand(blocksHeight(blocks.slice(fromBlock, end)));
|
|
4415
|
+
};
|
|
4416
|
+
balanceIfEndsContinuous(0);
|
|
2395
4417
|
for (let blockIdx = 0; blockIdx < blocks.length; blockIdx++) {
|
|
2396
4418
|
while (asm.secIdx < sectionCtxs.length - 1 && blockIdx >= asm.ctx.endIndex) {
|
|
2397
|
-
asm.
|
|
4419
|
+
const next = sectionCtxs[asm.secIdx + 1];
|
|
4420
|
+
if (next.continuous && next.pageWidth === asm.ctx.pageWidth && next.pageHeight === asm.ctx.pageHeight) {
|
|
4421
|
+
const end = Math.min(next.endIndex, blocks.length);
|
|
4422
|
+
asm.startBandSection(next, blocksHeight(blocks.slice(blockIdx, end)));
|
|
4423
|
+
continue;
|
|
4424
|
+
}
|
|
4425
|
+
if (asm.pageHasContent() || asm.pageInSection === 0) asm.flushPage(true);
|
|
4426
|
+
else asm.dropPage();
|
|
2398
4427
|
asm.secIdx++;
|
|
2399
4428
|
asm.ctx = sectionCtxs[asm.secIdx];
|
|
4429
|
+
asm.restartPageNumbers(asm.ctx);
|
|
2400
4430
|
asm.pageInSection = 0;
|
|
2401
4431
|
asm.cursorY = asm.ctx.pageHeight - asm.ctx.marginTop;
|
|
4432
|
+
balanceIfEndsContinuous(blockIdx);
|
|
2402
4433
|
}
|
|
2403
4434
|
const block = blocks[blockIdx];
|
|
2404
4435
|
if (asm.pendingPageBreak) {
|
|
2405
4436
|
asm.pendingPageBreak = false;
|
|
2406
|
-
|
|
4437
|
+
asm.flushPage(true);
|
|
2407
4438
|
}
|
|
2408
4439
|
if (builder && !(block.kind === "paragraph" && block.list)) asm.listStack.length = 0;
|
|
2409
4440
|
if (block.kind === "paragraph") {
|
|
2410
|
-
if (block.resolved.pageBreakBefore && asm.pageHasContent()) asm.flushPage();
|
|
4441
|
+
if ((block.resolved.pageBreakBefore || block.pageBreakBefore) && asm.pageHasContent()) asm.flushPage();
|
|
2411
4442
|
asm.cursorY -= block.spacingBeforePt;
|
|
2412
4443
|
let pb = block;
|
|
4444
|
+
const colW = asm.colWidth();
|
|
4445
|
+
if (reflowParagraph && pb.source && Math.abs(colW - asm.builtWidth()) > .5) pb = reflowParagraph(pb.source, colW);
|
|
2413
4446
|
if (reflowParagraph && pb.source && asm.exclusions.length > 0) {
|
|
2414
|
-
const widths = asm.lineWidthsFor(
|
|
2415
|
-
if (widths) pb = reflowParagraph(pb.source,
|
|
4447
|
+
const widths = asm.lineWidthsFor(pb, asm.cursorY);
|
|
4448
|
+
if (widths) pb = reflowParagraph(pb.source, colW, widths);
|
|
2416
4449
|
}
|
|
4450
|
+
const borderTopY = asm.cursorY;
|
|
4451
|
+
const borderPage = asm.pages.length;
|
|
2417
4452
|
let structId;
|
|
2418
4453
|
let markerLblId;
|
|
2419
4454
|
const firstLine = pb.lines[0];
|
|
@@ -2428,7 +4463,9 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2428
4463
|
if (lang && lang !== defaultLang) builder.node(structId).lang = lang;
|
|
2429
4464
|
}
|
|
2430
4465
|
let firstLineOfBlock = true;
|
|
2431
|
-
|
|
4466
|
+
let pairPending;
|
|
4467
|
+
for (const [lineIdx, line] of pb.lines.entries()) {
|
|
4468
|
+
if (pb.columnBreakLines?.has(lineIdx) && asm.colHasContent()) asm.advanceColumn();
|
|
2432
4469
|
const h = computeLineHeight(line, pb.resolved);
|
|
2433
4470
|
let newNotes = asm.lineFootnotes(line);
|
|
2434
4471
|
const addedReserve = (sub) => sub.reduce((sum, x) => sum + x.heightPt, 0) + (asm.pageNotes.length === 0 && sub.length > 0 ? FOOTNOTE_SEPARATOR_HEIGHT : 0);
|
|
@@ -2456,11 +4493,22 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2456
4493
|
});
|
|
2457
4494
|
}
|
|
2458
4495
|
}
|
|
2459
|
-
|
|
4496
|
+
const pairedNow = pairPending;
|
|
4497
|
+
pairPending = void 0;
|
|
4498
|
+
if (pairedNow === void 0) {
|
|
4499
|
+
for (let guard = 0; guard < 8 && asm.exclusions.length > 0; guard++) {
|
|
4500
|
+
const blocked = asm.lineGeometryAt(asm.cursorY, h).blockedUntilYUp;
|
|
4501
|
+
if (blocked === void 0 || blocked >= asm.cursorY) break;
|
|
4502
|
+
asm.cursorY = blocked;
|
|
4503
|
+
}
|
|
4504
|
+
asm.cursorY -= h;
|
|
4505
|
+
}
|
|
2460
4506
|
const indentLeft = pb.resolved.indentLeft + (line.firstLine ? pb.resolved.indentFirstLine : 0);
|
|
2461
4507
|
const offset = alignmentOffset(pb.resolved.alignment, line.contentWidthPt, line.availableWidthPt);
|
|
2462
|
-
const baselineY = pt(asm.ctx.pageHeight - (asm.cursorY +
|
|
2463
|
-
const
|
|
4508
|
+
const baselineY = pt(asm.ctx.pageHeight - (asm.cursorY + lineBaselineOffset(line, pb.resolved)));
|
|
4509
|
+
const geometry = asm.exclusions.length > 0 ? asm.lineGeometryAt(asm.cursorY + h, h) : void 0;
|
|
4510
|
+
const exclusionShift = pairedNow ?? geometry?.xOffset ?? 0;
|
|
4511
|
+
if (pairedNow === void 0 && geometry?.pair && Math.abs(line.availableWidthPt - geometry.width) < 1) pairPending = geometry.pair.xOffset;
|
|
2464
4512
|
const originX = asm.colLeft() + indentLeft + offset + exclusionShift;
|
|
2465
4513
|
if (markerLblId !== void 0 && line === firstLine) {
|
|
2466
4514
|
let k = 0;
|
|
@@ -2499,87 +4547,54 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2499
4547
|
baselineY,
|
|
2500
4548
|
...structId !== void 0 ? { structId } : {}
|
|
2501
4549
|
});
|
|
4550
|
+
asm.emitLineNumber(baselineY, line);
|
|
2502
4551
|
}
|
|
4552
|
+
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));
|
|
2503
4553
|
asm.cursorY -= pb.spacingAfterPt;
|
|
2504
4554
|
if (pb.pageBreakAfter) asm.pendingPageBreak = true;
|
|
2505
4555
|
} else if (block.kind === "image") {
|
|
2506
4556
|
const figId = builder ? createFigure(builder, block.altText, "Image") : void 0;
|
|
2507
4557
|
const emitImageAt = (x, topYUp, sink) => {
|
|
4558
|
+
const ins = block.drawInset;
|
|
2508
4559
|
sink.push({
|
|
2509
4560
|
type: "image",
|
|
2510
|
-
x: pt(x),
|
|
2511
|
-
y: pt(asm.ctx.pageHeight - topYUp),
|
|
2512
|
-
width: pt(block.widthPt),
|
|
2513
|
-
height: pt(block.heightPt),
|
|
4561
|
+
x: pt(x + (ins?.dxPt ?? 0)),
|
|
4562
|
+
y: pt(asm.ctx.pageHeight - (topYUp - (ins?.dyTopPt ?? 0))),
|
|
4563
|
+
width: pt(ins?.widthPt ?? block.widthPt),
|
|
4564
|
+
height: pt(ins?.heightPt ?? block.heightPt),
|
|
2514
4565
|
imageResourceName: block.resourceName,
|
|
4566
|
+
...block.crop ? { crop: block.crop } : {},
|
|
4567
|
+
...block.wash ? { wash: block.wash } : {},
|
|
4568
|
+
...block.rotationDeg ? { rotationDeg: block.rotationDeg } : {},
|
|
4569
|
+
...block.flipH ? { flipH: true } : {},
|
|
4570
|
+
...block.flipV ? { flipV: true } : {},
|
|
4571
|
+
...block.flipH ? { flipH: true } : {},
|
|
4572
|
+
...block.flipV ? { flipV: true } : {},
|
|
2515
4573
|
...figId !== void 0 ? { structId: figId } : {}
|
|
2516
4574
|
});
|
|
4575
|
+
if (block.outline) sink.push(...pictureOutlineItems(block.outline, x + (ins?.dxPt ?? 0), asm.ctx.pageHeight - (topYUp - (ins?.dyTopPt ?? 0)), ins?.widthPt ?? block.widthPt, ins?.heightPt ?? block.heightPt, figId));
|
|
2517
4576
|
};
|
|
2518
4577
|
if (isOutOfFlowFloat(block.float)) {
|
|
2519
4578
|
const fx = asm.floatX(block.float, block.widthPt);
|
|
2520
|
-
const fy = asm.floatTopYUp(block.float);
|
|
2521
|
-
emitImageAt(fx, fy, block.float.behind ? asm.floatsBehind : asm.floatsFront);
|
|
2522
|
-
if (block.float.wrap !== "none") asm.
|
|
2523
|
-
x0: fx,
|
|
2524
|
-
x1: fx + block.widthPt,
|
|
2525
|
-
topYUp: fy,
|
|
2526
|
-
bottomYUp: fy - block.heightPt
|
|
2527
|
-
});
|
|
4579
|
+
const fy = asm.floatTopYUp(block.float, block.heightPt);
|
|
4580
|
+
emitImageAt(fx, fy, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
|
|
4581
|
+
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
|
|
2528
4582
|
} else {
|
|
2529
4583
|
asm.cursorY -= block.spacingBeforePt;
|
|
2530
4584
|
if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
|
|
2531
4585
|
asm.cursorY -= block.heightPt;
|
|
2532
|
-
|
|
4586
|
+
const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, asm.colWidth());
|
|
4587
|
+
emitImageAt(asm.colLeft() + offset, asm.cursorY + block.heightPt, asm.current);
|
|
2533
4588
|
asm.cursorY -= block.spacingAfterPt;
|
|
2534
4589
|
}
|
|
2535
4590
|
} else if (block.kind === "shape") {
|
|
2536
4591
|
const figId = builder ? createFigure(builder, block.altText, "Shape") : void 0;
|
|
2537
|
-
const emitShapeAt = (x, bottomYUp, sink) =>
|
|
2538
|
-
const transform = flipTransform(buildShapeTransform(x, bottomYUp, block.widthPt, block.heightPt, block.rotation60k, block.flipH, block.flipV), asm.ctx.pageHeight);
|
|
2539
|
-
sink.push({
|
|
2540
|
-
type: "shape",
|
|
2541
|
-
shape: {
|
|
2542
|
-
paths: block.paths,
|
|
2543
|
-
...block.fillColorHex ? { fillColorHex: block.fillColorHex } : {},
|
|
2544
|
-
...block.fillGradient ? { fillGradient: block.fillGradient } : {},
|
|
2545
|
-
...block.stroke ? { stroke: block.stroke } : {},
|
|
2546
|
-
...block.shadow ? { shadow: block.shadow } : {},
|
|
2547
|
-
transform
|
|
2548
|
-
},
|
|
2549
|
-
...figId !== void 0 ? { structId: figId } : {}
|
|
2550
|
-
});
|
|
2551
|
-
if (block.textLines.length > 0) {
|
|
2552
|
-
const shapeBottom = bottomYUp;
|
|
2553
|
-
const shapeTop = bottomYUp + block.heightPt;
|
|
2554
|
-
const innerWidth = Math.max(1, block.widthPt - block.insetLeftPt - block.insetRightPt);
|
|
2555
|
-
let textY;
|
|
2556
|
-
if (block.anchor === "b") textY = shapeBottom + block.insetBottomPt + block.textHeightPt;
|
|
2557
|
-
else if (block.anchor === "ctr") textY = shapeBottom + (block.heightPt + block.textHeightPt) / 2;
|
|
2558
|
-
else textY = shapeTop - block.insetTopPt;
|
|
2559
|
-
for (const line of block.textLines) {
|
|
2560
|
-
const h = computeLineHeight(line, line.resolved);
|
|
2561
|
-
textY -= h;
|
|
2562
|
-
const lineOffset = alignmentOffset(line.resolved.alignment, line.contentWidthPt, innerWidth);
|
|
2563
|
-
sink.push({
|
|
2564
|
-
type: "line",
|
|
2565
|
-
line,
|
|
2566
|
-
originX: pt(x + block.insetLeftPt + lineOffset),
|
|
2567
|
-
baselineY: pt(asm.ctx.pageHeight - (textY + lineDescent(line))),
|
|
2568
|
-
...figId !== void 0 ? { structId: figId } : {}
|
|
2569
|
-
});
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
};
|
|
4592
|
+
const emitShapeAt = (x, bottomYUp, sink) => emitShapeItems(block, x, bottomYUp, sink, asm.ctx.pageHeight, figId);
|
|
2573
4593
|
if (isOutOfFlowFloat(block.float)) {
|
|
2574
4594
|
const fx = asm.floatX(block.float, block.widthPt);
|
|
2575
|
-
const fy = asm.floatTopYUp(block.float);
|
|
2576
|
-
emitShapeAt(fx, fy - block.heightPt, block.float.behind ? asm.floatsBehind : asm.floatsFront);
|
|
2577
|
-
if (block.float.wrap !== "none") asm.
|
|
2578
|
-
x0: fx,
|
|
2579
|
-
x1: fx + block.widthPt,
|
|
2580
|
-
topYUp: fy,
|
|
2581
|
-
bottomYUp: fy - block.heightPt
|
|
2582
|
-
});
|
|
4595
|
+
const fy = asm.floatTopYUp(block.float, block.heightPt);
|
|
4596
|
+
emitShapeAt(fx, fy - block.heightPt, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
|
|
4597
|
+
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
|
|
2583
4598
|
} else {
|
|
2584
4599
|
asm.cursorY -= block.spacingBeforePt;
|
|
2585
4600
|
if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
|
|
@@ -2589,45 +4604,15 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2589
4604
|
asm.cursorY -= block.spacingAfterPt;
|
|
2590
4605
|
}
|
|
2591
4606
|
} else if (block.kind === "chart") {
|
|
2592
|
-
const figId = builder ? createFigure(builder, block.altText, "Chart") : void 0;
|
|
2593
|
-
const fig = figId !== void 0 ? { structId: figId } : {};
|
|
4607
|
+
const figId = builder ? createFigure(builder, block.altText, block.figureRole ?? "Chart") : void 0;
|
|
2594
4608
|
const emitChartAt = (x, bottomYUp, sink) => {
|
|
2595
|
-
|
|
2596
|
-
type: "shape",
|
|
2597
|
-
shape: {
|
|
2598
|
-
paths: s.paths,
|
|
2599
|
-
...s.fillColorHex ? { fillColorHex: s.fillColorHex } : {},
|
|
2600
|
-
...s.stroke ? { stroke: s.stroke } : {},
|
|
2601
|
-
transform: flipTransform([
|
|
2602
|
-
1,
|
|
2603
|
-
0,
|
|
2604
|
-
0,
|
|
2605
|
-
1,
|
|
2606
|
-
x,
|
|
2607
|
-
bottomYUp
|
|
2608
|
-
], asm.ctx.pageHeight)
|
|
2609
|
-
},
|
|
2610
|
-
...fig
|
|
2611
|
-
});
|
|
2612
|
-
for (const t of block.layout.texts) sink.push({
|
|
2613
|
-
type: "line",
|
|
2614
|
-
line: t.line,
|
|
2615
|
-
originX: pt(x + t.x),
|
|
2616
|
-
baselineY: pt(asm.ctx.pageHeight - (bottomYUp + t.y)),
|
|
2617
|
-
...t.rotationDeg ? { rotationDeg: t.rotationDeg } : {},
|
|
2618
|
-
...fig
|
|
2619
|
-
});
|
|
4609
|
+
sink.push(...chartPageItems(block, x, bottomYUp, asm.ctx.pageHeight, figId));
|
|
2620
4610
|
};
|
|
2621
4611
|
if (isOutOfFlowFloat(block.float)) {
|
|
2622
4612
|
const fx = asm.floatX(block.float, block.widthPt);
|
|
2623
|
-
const fy = asm.floatTopYUp(block.float);
|
|
2624
|
-
emitChartAt(fx, fy - block.heightPt, block.float.behind ? asm.floatsBehind : asm.floatsFront);
|
|
2625
|
-
if (block.float.wrap !== "none") asm.
|
|
2626
|
-
x0: fx,
|
|
2627
|
-
x1: fx + block.widthPt,
|
|
2628
|
-
topYUp: fy,
|
|
2629
|
-
bottomYUp: fy - block.heightPt
|
|
2630
|
-
});
|
|
4613
|
+
const fy = asm.floatTopYUp(block.float, block.heightPt);
|
|
4614
|
+
emitChartAt(fx, fy - block.heightPt, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
|
|
4615
|
+
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
|
|
2631
4616
|
} else {
|
|
2632
4617
|
asm.cursorY -= block.spacingBeforePt;
|
|
2633
4618
|
if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
|
|
@@ -2638,6 +4623,19 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2638
4623
|
}
|
|
2639
4624
|
} else {
|
|
2640
4625
|
const colCount = block.rows.reduce((max, r) => Math.max(max, r.cells.reduce((s, c) => Math.max(s, c.colStart + c.colSpan), 0)), 0);
|
|
4626
|
+
const anchorY = isOutOfFlowFloat(block.float) ? asm.floatTopYUp(block.float, block.heightPt) : 0;
|
|
4627
|
+
if (isOutOfFlowFloat(block.float) && anchorY - block.heightPt >= asm.ctx.marginBottom) {
|
|
4628
|
+
const fx = asm.floatX(block.float, block.totalWidthPt);
|
|
4629
|
+
const fy = anchorY;
|
|
4630
|
+
const sink = PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder);
|
|
4631
|
+
let rowY = fy;
|
|
4632
|
+
for (const row of block.rows) {
|
|
4633
|
+
emitRowChunk(sink, row, fx, rowY, asm.ctx.pageHeight, colCount, void 0, void 0, cellAnchorCtx(asm));
|
|
4634
|
+
rowY -= row.heightPt;
|
|
4635
|
+
}
|
|
4636
|
+
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.totalWidthPt, block.heightPt);
|
|
4637
|
+
continue;
|
|
4638
|
+
}
|
|
2641
4639
|
const headerRows = [];
|
|
2642
4640
|
for (const r of block.rows) if (r.isHeader) headerRows.push(r);
|
|
2643
4641
|
else break;
|
|
@@ -2661,7 +4659,9 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2661
4659
|
return b.create("P", cellNode).id;
|
|
2662
4660
|
});
|
|
2663
4661
|
}
|
|
2664
|
-
const
|
|
4662
|
+
const room = asm.cursorY - asm.bottomLimit();
|
|
4663
|
+
const page = asm.ctx.pageContentHeight;
|
|
4664
|
+
const chunks = !row.cantSplit && row.heightPt > room && asm.colHasContent() && room > 0 ? splitRowIntoChunks(row, room, page) : row.heightPt > page ? splitRowIntoChunks(row, page) : [row];
|
|
2665
4665
|
for (let ci = 0; ci < chunks.length; ci++) {
|
|
2666
4666
|
const chunk = chunks[ci];
|
|
2667
4667
|
const forcedBreak = ci === 0 && row.breakBefore && !isLeadingHeader && asm.current.length > 0;
|
|
@@ -2675,7 +4675,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2675
4675
|
}
|
|
2676
4676
|
}
|
|
2677
4677
|
const mergedHeights = chunks.length === 1 && row.cells.some((c) => c.mergeRole === "start") ? row.cells.map((c) => c.mergeRole === "start" ? mergedBoxHeightPt(block.rows, ri, c.colStart, asm.cursorY - asm.bottomLimit()) : void 0) : void 0;
|
|
2678
|
-
emitRowChunk(asm.current, chunk, tableX, asm.cursorY, asm.ctx.pageHeight, colCount, cellStructIds, mergedHeights);
|
|
4678
|
+
emitRowChunk(asm.current, chunk, tableX, asm.cursorY, asm.ctx.pageHeight, colCount, cellStructIds, mergedHeights, cellAnchorCtx(asm));
|
|
2679
4679
|
asm.cursorY -= chunk.heightPt;
|
|
2680
4680
|
if (ci === chunks.length - 1 && ri < block.rows.length - 1) {
|
|
2681
4681
|
const next = block.rows[ri + 1];
|
|
@@ -2744,7 +4744,7 @@ function tableCellRowSpan(rows, startRowIdx, colStart) {
|
|
|
2744
4744
|
}
|
|
2745
4745
|
return span;
|
|
2746
4746
|
}
|
|
2747
|
-
function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellStructIds, mergedHeights) {
|
|
4747
|
+
function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellStructIds, mergedHeights, anchored) {
|
|
2748
4748
|
const rowTop = cursorY;
|
|
2749
4749
|
const rowBottom = cursorY - row.heightPt;
|
|
2750
4750
|
{
|
|
@@ -2917,7 +4917,49 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
|
|
|
2917
4917
|
const slack = cell.verticalAlign !== void 0 ? boxHeightPt - cell.padTopPt - cell.contentHeightPt - cell.padBottomPt : 0;
|
|
2918
4918
|
const vOffset = slack <= 0 ? 0 : cell.verticalAlign === "bottom" ? slack : cell.verticalAlign === "center" ? slack / 2 : 0;
|
|
2919
4919
|
let textY = rowTop - cell.padTopPt - vOffset;
|
|
2920
|
-
|
|
4920
|
+
const nestedX = cellX + cell.padLeftPt;
|
|
4921
|
+
const drawNestedBefore = (n) => {
|
|
4922
|
+
for (const nt of cell.nestedTables ?? []) {
|
|
4923
|
+
if (nt.afterLine !== n) continue;
|
|
4924
|
+
for (const nrow of nt.block.rows) {
|
|
4925
|
+
const nestedIds = structId !== void 0 ? nrow.cells.map(() => structId) : void 0;
|
|
4926
|
+
emitRowChunk(out, nrow, nestedX, textY, pageHeight, nt.block.colCount, nestedIds, void 0, anchored);
|
|
4927
|
+
textY -= nrow.heightPt;
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
for (const cs of cell.shapes ?? []) {
|
|
4931
|
+
if (cs.afterLine !== n) continue;
|
|
4932
|
+
const offset = alignmentOffset(cs.block.resolvedAlignment, cs.block.widthPt, cell.widthPt - cell.padLeftPt - cell.padRightPt);
|
|
4933
|
+
textY -= cs.block.heightPt;
|
|
4934
|
+
emitShapeItems(cs.block, nestedX + offset, textY, out, pageHeight, structId);
|
|
4935
|
+
}
|
|
4936
|
+
for (const cf of cell.floats ?? []) {
|
|
4937
|
+
if (cf.afterLine !== n || !anchored) continue;
|
|
4938
|
+
const inner = cell.widthPt - cell.padLeftPt - cell.padRightPt;
|
|
4939
|
+
const cellTop = rowTop - cell.padTopPt;
|
|
4940
|
+
const cellBottom = rowTop - (mergedHeights?.[i] ?? row.heightPt) + cell.padBottomPt;
|
|
4941
|
+
const own = cf.block.float?.inCell !== false;
|
|
4942
|
+
emitCellFloat(cf, anchored, {
|
|
4943
|
+
...anchored.frame,
|
|
4944
|
+
...own ? {
|
|
4945
|
+
pageLeft: nestedX,
|
|
4946
|
+
pageWidth: inner,
|
|
4947
|
+
pageTopYUp: cellTop,
|
|
4948
|
+
pageBottomYUp: cellBottom,
|
|
4949
|
+
marginLeft: nestedX,
|
|
4950
|
+
contentWidth: inner,
|
|
4951
|
+
marginTopYUp: cellTop,
|
|
4952
|
+
marginBottomYUp: cellBottom
|
|
4953
|
+
} : {},
|
|
4954
|
+
columnLeft: nestedX,
|
|
4955
|
+
columnWidth: inner,
|
|
4956
|
+
anchorY: textY
|
|
4957
|
+
});
|
|
4958
|
+
}
|
|
4959
|
+
};
|
|
4960
|
+
for (const [lineIdx, line] of cell.lines.entries()) {
|
|
4961
|
+
drawNestedBefore(lineIdx);
|
|
4962
|
+
textY -= cell.lineGaps?.get(lineIdx) ?? 0;
|
|
2921
4963
|
const h = computeLineHeight(line, line.resolved);
|
|
2922
4964
|
textY -= h;
|
|
2923
4965
|
const offset = alignmentOffset(line.resolved.alignment, line.contentWidthPt, cell.widthPt - cell.padLeftPt - cell.padRightPt);
|
|
@@ -2926,7 +4968,7 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
|
|
|
2926
4968
|
type: "line",
|
|
2927
4969
|
line,
|
|
2928
4970
|
originX: pt(cellX + cell.padLeftPt + indentLeft + offset),
|
|
2929
|
-
baselineY: pt(pageHeight - (textY +
|
|
4971
|
+
baselineY: pt(pageHeight - (textY + lineBaselineOffset(line, line.resolved))),
|
|
2930
4972
|
...cell.clipped ? { clip: {
|
|
2931
4973
|
x: pt(cellX),
|
|
2932
4974
|
y: pt(pageHeight - rowTop),
|
|
@@ -2936,20 +4978,16 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
|
|
|
2936
4978
|
...structId !== void 0 ? { structId } : {}
|
|
2937
4979
|
});
|
|
2938
4980
|
}
|
|
2939
|
-
|
|
2940
|
-
const nestedX = cellX + cell.padLeftPt;
|
|
2941
|
-
for (const nt of cell.nestedTables) for (const nrow of nt.rows) {
|
|
2942
|
-
const nestedIds = structId !== void 0 ? nrow.cells.map(() => structId) : void 0;
|
|
2943
|
-
emitRowChunk(out, nrow, nestedX, textY, pageHeight, nt.colCount, nestedIds);
|
|
2944
|
-
textY -= nrow.heightPt;
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
4981
|
+
drawNestedBefore(cell.lines.length);
|
|
2947
4982
|
}
|
|
2948
4983
|
}
|
|
2949
|
-
function splitRowIntoChunks(row, capacity) {
|
|
4984
|
+
function splitRowIntoChunks(row, capacity, restCapacity = capacity) {
|
|
2950
4985
|
if (row.cells.some((c) => c.nestedTables && c.nestedTables.length > 0)) return [row];
|
|
2951
4986
|
const queues = row.cells.map((c) => ({
|
|
2952
|
-
remaining:
|
|
4987
|
+
remaining: c.lines.map((line, i) => ({
|
|
4988
|
+
line,
|
|
4989
|
+
gapPt: c.lineGaps?.get(i) ?? 0
|
|
4990
|
+
})),
|
|
2953
4991
|
template: c
|
|
2954
4992
|
}));
|
|
2955
4993
|
const anyHasLines = () => queues.some((q) => q.template.mergeRole === "middle" || q.template.mergeRole === "end" ? false : q.remaining.length > 0);
|
|
@@ -2967,23 +5005,31 @@ function splitRowIntoChunks(row, capacity) {
|
|
|
2967
5005
|
continue;
|
|
2968
5006
|
}
|
|
2969
5007
|
const padTop = isFirst ? tpl.padTopPt : 0;
|
|
2970
|
-
const capacityForLines = Math.max(0, capacity - padTop);
|
|
5008
|
+
const capacityForLines = Math.max(0, (isFirst ? capacity : restCapacity) - padTop);
|
|
2971
5009
|
const taken = [];
|
|
2972
5010
|
let takenHeight = 0;
|
|
2973
5011
|
while (q.remaining.length > 0) {
|
|
2974
5012
|
const next = q.remaining[0];
|
|
2975
|
-
const lh = computeLineHeight(next, next.resolved);
|
|
5013
|
+
const lh = computeLineHeight(next.line, next.line.resolved) + next.gapPt;
|
|
2976
5014
|
if (taken.length > 0 && takenHeight + lh > capacityForLines) break;
|
|
2977
5015
|
taken.push(next);
|
|
2978
5016
|
takenHeight += lh;
|
|
2979
5017
|
q.remaining.shift();
|
|
2980
5018
|
}
|
|
2981
5019
|
const cellHeight = padTop + takenHeight;
|
|
5020
|
+
const gaps = /* @__PURE__ */ new Map();
|
|
5021
|
+
taken.forEach((t, li) => {
|
|
5022
|
+
if (t.gapPt > 0) gaps.set(li, t.gapPt);
|
|
5023
|
+
});
|
|
5024
|
+
const { lineGaps: _g, shapes: _s, floats: _f, ...restOfCell } = tpl;
|
|
2982
5025
|
const chunkCell = {
|
|
2983
|
-
...
|
|
5026
|
+
...restOfCell,
|
|
5027
|
+
...isFirst && tpl.shapes ? { shapes: tpl.shapes } : {},
|
|
5028
|
+
...isFirst && tpl.floats ? { floats: tpl.floats } : {},
|
|
5029
|
+
...gaps.size > 0 ? { lineGaps: gaps } : {},
|
|
2984
5030
|
padTopPt: padTop,
|
|
2985
5031
|
padBottomPt: 0,
|
|
2986
|
-
lines: taken,
|
|
5032
|
+
lines: taken.map((t) => t.line),
|
|
2987
5033
|
contentHeightPt: takenHeight,
|
|
2988
5034
|
totalHeightPt: cellHeight
|
|
2989
5035
|
};
|
|
@@ -3022,7 +5068,8 @@ function splitRowIntoChunks(row, capacity) {
|
|
|
3022
5068
|
cells: chunkCells,
|
|
3023
5069
|
columnXOffsets: row.columnXOffsets,
|
|
3024
5070
|
rowIdx: row.rowIdx,
|
|
3025
|
-
rowCount: row.rowCount
|
|
5071
|
+
rowCount: row.rowCount,
|
|
5072
|
+
...row.isHeader ? { isHeader: true } : {}
|
|
3026
5073
|
});
|
|
3027
5074
|
isFirst = false;
|
|
3028
5075
|
}
|
|
@@ -3147,4 +5194,4 @@ function emitRowBottomEdge(out, row, marginLeft, rowBottom, pageHeight) {
|
|
|
3147
5194
|
}
|
|
3148
5195
|
}
|
|
3149
5196
|
//#endregion
|
|
3150
|
-
export { layoutStyledDocument };
|
|
5197
|
+
export { GLUE_SHRINK_RATIO, layoutStyledDocument };
|