reamkit 1.16.0 → 1.17.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 +473 -7
- 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 +181 -0
- package/dist/esm/core/ir/flow.d.ts +23 -1
- 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/vector.d.ts +17 -0
- package/dist/esm/excel/print-model.js +1 -1
- 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 +18 -4
- package/dist/esm/layout/page-doc.d.ts +62 -2
- package/dist/esm/layout/styled-layout.d.ts +44 -2
- package/dist/esm/layout/styled-layout.js +1870 -228
- package/dist/esm/pdf/shading.js +76 -6
- package/dist/esm/pdf/styled-page-emitter.js +217 -43
- package/dist/esm/pdf/vector-graphics.d.ts +28 -1
- package/dist/esm/pdf/vector-graphics.js +94 -11
- 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 +1 -1
- package/dist/esm/word/document-parser.d.ts +96 -7
- package/dist/esm/word/document-parser.js +537 -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 +53 -5
- package/dist/esm/word/drawing-parser.js +1540 -54
- 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
|
@@ -10,17 +10,18 @@ import { FORCED_BREAK, breakLines } from "../core/line-breaker/knuth-plass.js";
|
|
|
10
10
|
import { greedyBreakLines } from "../core/line-breaker/greedy.js";
|
|
11
11
|
import { cjkBreakBetween, splitCjkSegment } from "../core/line-breaker/cjk.js";
|
|
12
12
|
import "../core/line-breaker/index.js";
|
|
13
|
-
import { applyNumbering, applyNumberingToHeadersFooters } from "../core/numbering/apply.js";
|
|
14
13
|
import { DEFAULT_RESOLVED_PARAGRAPH, DEFAULT_RESOLVED_RUN } from "../core/style-cascade/types.js";
|
|
15
14
|
import { resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
|
|
16
15
|
import "../core/style-cascade/index.js";
|
|
16
|
+
import { applyNumbering, applyNumberingToHeadersFooters } from "../core/numbering/apply.js";
|
|
17
|
+
import "../core/numbering/index.js";
|
|
17
18
|
import { PathBuilder, flipTransform } from "../core/vector.js";
|
|
18
19
|
import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
|
|
19
20
|
import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
|
|
20
21
|
import { buildChartScene, legendSeriesName } from "../core/drawingml/chart-geometry.js";
|
|
21
22
|
import { layoutMath, mathGlyphSegments, variantStyle } from "./math-layout.js";
|
|
22
23
|
import { rectPath } from "../core/drawingml/preset-geometry.js";
|
|
23
|
-
import { buildShapePaths, buildShapeTransform, buildStroke, gradientToSolid } from "../core/drawingml/shape-render.js";
|
|
24
|
+
import { buildShapePaths, buildShapeTransform, buildStroke, gradientToSolid, lineEndPaths } from "../core/drawingml/shape-render.js";
|
|
24
25
|
import { StructTreeBuilder } from "../pdf/struct-tree.js";
|
|
25
26
|
//#region src/layout/styled-layout.ts
|
|
26
27
|
function parsePdfAProfile(pdfA) {
|
|
@@ -33,6 +34,14 @@ function parsePdfAProfile(pdfA) {
|
|
|
33
34
|
version: part === 1 ? "1.4" : "1.7"
|
|
34
35
|
};
|
|
35
36
|
}
|
|
37
|
+
/** §17.6.4 — how heavy Word draws the rule between columns. */
|
|
38
|
+
var COLUMN_SEPARATOR_PT = .5;
|
|
39
|
+
/**
|
|
40
|
+
* How far a space may be squeezed below its natural width — the shrink the
|
|
41
|
+
* line breaker offers when it weighs a line, and therefore the shrink the
|
|
42
|
+
* emitter owes it back when it draws one.
|
|
43
|
+
*/
|
|
44
|
+
var GLUE_SHRINK_RATIO = .3;
|
|
36
45
|
var TWIP_TO_PT = 1 / 20;
|
|
37
46
|
var EIGHTH_PT = 1 / 8;
|
|
38
47
|
var DEFAULT_CELL_PADDING_TWIPS = 108;
|
|
@@ -40,6 +49,7 @@ var DEFAULT_BORDER_SIZE_EIGHTH = 4;
|
|
|
40
49
|
var FOOTNOTE_RULE_PT = .75;
|
|
41
50
|
var FOOTNOTE_RULE_GAP_ABOVE = 4;
|
|
42
51
|
var FOOTNOTE_SEPARATOR_HEIGHT = 10;
|
|
52
|
+
var LINE_NUMBER_GAP_PT = 18;
|
|
43
53
|
var FLOAT_TEXT_GAP = 6;
|
|
44
54
|
var MIN_WRAP_WIDTH = 36;
|
|
45
55
|
function isOutOfFlowFloat(f) {
|
|
@@ -257,21 +267,48 @@ function commentTailBlocks(comment, n, opts) {
|
|
|
257
267
|
*/
|
|
258
268
|
function layoutStyledDocument(body, options) {
|
|
259
269
|
const sectionList = resolveSectionList(body, options);
|
|
260
|
-
const noteAssigned = assignNoteNumbers(applyNumbering(body, options.numbering), options.comments ? new Set(options.comments.keys()) : void 0);
|
|
270
|
+
const noteAssigned = assignNoteNumbers(applyNumbering(body, options.numbering, options.styles), options.comments ? new Set(options.comments.keys()) : void 0);
|
|
261
271
|
const numberedBody = noteAssigned.body;
|
|
262
|
-
const numberedHeadersFooters = applyNumberingToHeadersFooters(options.headersFooters, options.numbering);
|
|
272
|
+
const numberedHeadersFooters = applyNumberingToHeadersFooters(options.headersFooters, options.numbering, options.styles);
|
|
263
273
|
const pdfaProfile = options.pdfA ? parsePdfAProfile(options.pdfA) : void 0;
|
|
264
274
|
const tagged = options.tagged === true || options.pdfUA === true || (pdfaProfile?.tagged ?? false);
|
|
265
275
|
const structBuilder = tagged ? new StructTreeBuilder() : void 0;
|
|
266
276
|
const fontResources = collectFontResources(numberedBody, numberedHeadersFooters, options);
|
|
267
277
|
const imageResources = collectImageResources(numberedBody, numberedHeadersFooters, options);
|
|
268
|
-
const sectionCtxs = sectionList.map((s) => buildSectionContext(s, options, numberedHeadersFooters, fontResources));
|
|
278
|
+
const sectionCtxs = sectionList.map((s) => buildSectionContext(s, options, numberedHeadersFooters, fontResources, imageResources));
|
|
269
279
|
let sectionIdx = 0;
|
|
280
|
+
const bodyFrames = frameGroups(numberedBody);
|
|
270
281
|
const blocks = numberedBody.map((el, idx) => {
|
|
271
282
|
while (sectionIdx < sectionCtxs.length - 1 && idx >= sectionCtxs[sectionIdx].endIndex) sectionIdx++;
|
|
272
283
|
const ctx = sectionCtxs[sectionIdx];
|
|
273
|
-
|
|
284
|
+
const width = ctx.columns ? ctx.columns[0].widthPt : ctx.contentWidth;
|
|
285
|
+
const box = {
|
|
286
|
+
pageWidthPt: ctx.pageWidth,
|
|
287
|
+
pageHeightPt: ctx.pageHeight,
|
|
288
|
+
marginLeftPt: ctx.marginLeft,
|
|
289
|
+
marginRightPt: ctx.pageWidth - ctx.marginLeft - ctx.contentWidth,
|
|
290
|
+
marginTopPt: ctx.marginTop,
|
|
291
|
+
marginBottomPt: ctx.marginBottom
|
|
292
|
+
};
|
|
293
|
+
const group = bodyFrames.starts.get(idx);
|
|
294
|
+
if (group) return layoutFrameBlock(group, ctx.options ?? options, fontResources, imageResources, width);
|
|
295
|
+
if (bodyFrames.continued.has(idx)) return emptyBlock(options);
|
|
296
|
+
return layoutBodyElement(el, ctx.options ?? options, fontResources, imageResources, width, ctx.pageContentHeight, box);
|
|
274
297
|
});
|
|
298
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
299
|
+
const here = blocks[i];
|
|
300
|
+
const next = blocks[i + 1];
|
|
301
|
+
if (here?.kind !== "paragraph" || next?.kind !== "paragraph") continue;
|
|
302
|
+
if (here.source?.properties.styleId !== next.source?.properties.styleId) continue;
|
|
303
|
+
if (here.resolved.contextualSpacing) blocks[i] = {
|
|
304
|
+
...here,
|
|
305
|
+
spacingAfterPt: 0
|
|
306
|
+
};
|
|
307
|
+
if (next.resolved.contextualSpacing) blocks[i + 1] = {
|
|
308
|
+
...next,
|
|
309
|
+
spacingBeforePt: 0
|
|
310
|
+
};
|
|
311
|
+
}
|
|
275
312
|
const noteBlockCache = /* @__PURE__ */ new Map();
|
|
276
313
|
const notePlan = options.footnotes && noteAssigned.footnotes.size > 0 ? {
|
|
277
314
|
numbers: noteAssigned.footnotes,
|
|
@@ -286,7 +323,7 @@ function layoutStyledDocument(body, options) {
|
|
|
286
323
|
const n = noteAssigned.footnotes.get(id);
|
|
287
324
|
let laid = void 0;
|
|
288
325
|
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));
|
|
326
|
+
const noteBlocks = substituteNoteNumber(content, n).map((el) => layoutBodyElement(el, sectionCtx.options ?? options, fontResources, imageResources, sectionCtx.contentWidth, sectionCtx.pageContentHeight));
|
|
290
327
|
laid = {
|
|
291
328
|
blocks: noteBlocks,
|
|
292
329
|
heightPt: noteBlocks.reduce((sum, b) => sum + (b.kind === "paragraph" ? b.spacingBeforePt + b.heightPt + b.spacingAfterPt : 0), 0)
|
|
@@ -314,7 +351,24 @@ function layoutStyledDocument(body, options) {
|
|
|
314
351
|
n
|
|
315
352
|
});
|
|
316
353
|
}
|
|
317
|
-
|
|
354
|
+
if (tailNotes.length > 0) blocks.push(layoutShapeBlock({
|
|
355
|
+
width: pt(FOOTNOTE_RULE_WIDTH),
|
|
356
|
+
height: pt(FOOTNOTE_RULE_PT),
|
|
357
|
+
geometry: {
|
|
358
|
+
kind: "preset",
|
|
359
|
+
preset: "rect",
|
|
360
|
+
adjust: /* @__PURE__ */ new Map()
|
|
361
|
+
},
|
|
362
|
+
fill: {
|
|
363
|
+
kind: "solid",
|
|
364
|
+
colorHex: "000000"
|
|
365
|
+
},
|
|
366
|
+
paragraphProperties: {
|
|
367
|
+
spacingBefore: pt(FOOTNOTE_SEPARATOR_HEIGHT),
|
|
368
|
+
spacingAfter: pt(FOOTNOTE_RULE_GAP_ABOVE)
|
|
369
|
+
}
|
|
370
|
+
}, options, fontResources, imageResources, lastCtx.contentWidth));
|
|
371
|
+
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
372
|
const commentNums = noteAssigned.comments;
|
|
319
373
|
const threadDepth = (id) => {
|
|
320
374
|
let depth = 0;
|
|
@@ -346,7 +400,7 @@ function layoutStyledDocument(body, options) {
|
|
|
346
400
|
const commentNotes = options.commentAnnotations && options.comments ? buildCommentNotes(noteAssigned.comments, options.comments) : void 0;
|
|
347
401
|
const reflowParagraph = (paragraph, width, widths) => layoutParagraphBlock(paragraph, options, fontResources, imageResources, width, widths);
|
|
348
402
|
return {
|
|
349
|
-
pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph),
|
|
403
|
+
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
404
|
resources: options.resources ?? new ResourceStore(),
|
|
351
405
|
fontResources,
|
|
352
406
|
imageResources,
|
|
@@ -367,14 +421,16 @@ function resolvePageDimensions(options, section) {
|
|
|
367
421
|
const sectionRight = section?.margins?.right !== void 0 ? section.margins.right : void 0;
|
|
368
422
|
const sectionTop = section?.margins?.top !== void 0 ? section.margins.top : void 0;
|
|
369
423
|
const sectionBottom = section?.margins?.bottom !== void 0 ? section.margins.bottom : void 0;
|
|
424
|
+
const gutter = section?.margins?.gutter ?? 0;
|
|
425
|
+
const atTop = options.gutterAtTop === true;
|
|
370
426
|
const headerOffsetPt = section?.margins?.header ?? 720 * TWIP_TO_PT;
|
|
371
427
|
const footerOffsetPt = section?.margins?.footer ?? 720 * TWIP_TO_PT;
|
|
372
428
|
return {
|
|
373
429
|
pageWidth: options.pageWidth ?? sectionPageWidth ?? 595,
|
|
374
430
|
pageHeight: options.pageHeight ?? sectionPageHeight ?? 842,
|
|
375
|
-
marginLeft: options.marginLeft ?? sectionLeft ?? 72,
|
|
431
|
+
marginLeft: (options.marginLeft ?? sectionLeft ?? 72) + (atTop ? 0 : gutter),
|
|
376
432
|
marginRight: options.marginRight ?? sectionRight ?? 72,
|
|
377
|
-
marginTop: options.marginTop ?? sectionTop ?? 72,
|
|
433
|
+
marginTop: (options.marginTop ?? sectionTop ?? 72) + (atTop ? gutter : 0),
|
|
378
434
|
marginBottom: options.marginBottom ?? sectionBottom ?? 72,
|
|
379
435
|
headerOffsetPt,
|
|
380
436
|
footerOffsetPt
|
|
@@ -394,13 +450,32 @@ function resolveSectionList(body, options) {
|
|
|
394
450
|
endIndex: body.length
|
|
395
451
|
}];
|
|
396
452
|
}
|
|
397
|
-
function buildSectionContext(section, options, headersFooters, fontResources) {
|
|
453
|
+
function buildSectionContext(section, options, headersFooters, fontResources, imageResources) {
|
|
398
454
|
const dims = resolvePageDimensions(options, section.properties);
|
|
399
455
|
const contentWidth = dims.pageWidth - dims.marginLeft - dims.marginRight;
|
|
400
|
-
const
|
|
401
|
-
|
|
456
|
+
const secOptions = section.properties.gridLinePitchPt !== void 0 ? {
|
|
457
|
+
...options,
|
|
458
|
+
gridLinePitchPt: section.properties.gridLinePitchPt
|
|
459
|
+
} : options;
|
|
460
|
+
const relativeBox = {
|
|
461
|
+
pageWidthPt: dims.pageWidth,
|
|
462
|
+
pageHeightPt: dims.pageHeight,
|
|
463
|
+
marginLeftPt: dims.marginLeft,
|
|
464
|
+
marginRightPt: dims.marginRight,
|
|
465
|
+
marginTopPt: dims.marginTop,
|
|
466
|
+
marginBottomPt: dims.marginBottom
|
|
467
|
+
};
|
|
468
|
+
const headerSet = layoutHeaderSet(section.properties, headersFooters, secOptions, fontResources, imageResources, contentWidth, dims.marginLeft, dims.pageHeight, dims.headerOffsetPt, {
|
|
469
|
+
top: dims.marginTop,
|
|
470
|
+
bottom: dims.marginBottom
|
|
471
|
+
}, relativeBox);
|
|
472
|
+
const footerSet = layoutFooterSet(section.properties, headersFooters, secOptions, fontResources, imageResources, contentWidth, dims.marginLeft, dims.pageHeight, dims.footerOffsetPt, {
|
|
473
|
+
top: dims.marginTop,
|
|
474
|
+
bottom: dims.marginBottom
|
|
475
|
+
}, relativeBox);
|
|
402
476
|
const columns = buildColumnGeometry(section.properties.columns, contentWidth);
|
|
403
|
-
const
|
|
477
|
+
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);
|
|
478
|
+
const headerBottom = headerHeight > 0 ? dims.headerOffsetPt + headerHeight : 0;
|
|
404
479
|
const marginTop = Math.max(dims.marginTop, headerBottom);
|
|
405
480
|
return {
|
|
406
481
|
endIndex: section.endIndex,
|
|
@@ -416,7 +491,9 @@ function buildSectionContext(section, options, headersFooters, fontResources) {
|
|
|
416
491
|
headerSet,
|
|
417
492
|
footerSet,
|
|
418
493
|
titlePg: section.properties.titlePg === true,
|
|
419
|
-
evenAndOddHeaders: section.properties.evenAndOddHeaders === true
|
|
494
|
+
evenAndOddHeaders: section.properties.evenAndOddHeaders === true,
|
|
495
|
+
continuous: section.properties.sectionStart === "continuous",
|
|
496
|
+
options: secOptions
|
|
420
497
|
};
|
|
421
498
|
}
|
|
422
499
|
function buildColumnGeometry(cols, contentWidth) {
|
|
@@ -451,7 +528,7 @@ function markPagination(cmds) {
|
|
|
451
528
|
artifact: "pagination"
|
|
452
529
|
}));
|
|
453
530
|
}
|
|
454
|
-
function layoutHeaderSet(section, headersFooters, options, fontResources, contentWidth, marginLeft, pageHeight, headerOffsetPt) {
|
|
531
|
+
function layoutHeaderSet(section, headersFooters, options, fontResources, imageResources, contentWidth, marginLeft, pageHeight, headerOffsetPt, bandMargins, relativeBox) {
|
|
455
532
|
const band = (type) => {
|
|
456
533
|
const ref = refByType(section.headers, type);
|
|
457
534
|
if (!ref) return { commands: [] };
|
|
@@ -459,9 +536,9 @@ function layoutHeaderSet(section, headersFooters, options, fontResources, conten
|
|
|
459
536
|
if (!content) return { commands: [] };
|
|
460
537
|
let measured = 0;
|
|
461
538
|
const render = (c) => {
|
|
462
|
-
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth);
|
|
539
|
+
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth, imageResources, relativeBox);
|
|
463
540
|
measured = blocksHeight(blocks);
|
|
464
|
-
return markPagination(drawBlocksSequentially(blocks, marginLeft, pageHeight - headerOffsetPt, pageHeight));
|
|
541
|
+
return markPagination(drawBlocksSequentially(blocks, marginLeft, pageHeight - headerOffsetPt, pageHeight, contentWidth, void 0, bandMargins));
|
|
465
542
|
};
|
|
466
543
|
if (contentHasPageFields(content)) {
|
|
467
544
|
render(substitutePageFields(content, 1, 1));
|
|
@@ -482,19 +559,37 @@ function layoutHeaderSet(section, headersFooters, options, fontResources, conten
|
|
|
482
559
|
even: band("even")
|
|
483
560
|
};
|
|
484
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* §17.6.4 — the height each column of a BALANCED band is given: the band's
|
|
564
|
+
* share, but never more than the page has room for, and never so little that
|
|
565
|
+
* the band could not fit the columns at all.
|
|
566
|
+
*
|
|
567
|
+
* @param bandHeightPt The band's whole laid-out height.
|
|
568
|
+
* @param colCount How many columns it has to share.
|
|
569
|
+
* @param roomPt What the page has left below the band's top.
|
|
570
|
+
* @returns The per-column height, or 0 when the band should just fill.
|
|
571
|
+
*/
|
|
572
|
+
function balancedColumnHeight(bandHeightPt, colCount, roomPt) {
|
|
573
|
+
if (bandHeightPt <= 0 || colCount < 2 || roomPt <= 0) return 0;
|
|
574
|
+
if (bandHeightPt > roomPt * colCount) return 0;
|
|
575
|
+
return Math.min(roomPt, bandHeightPt / colCount);
|
|
576
|
+
}
|
|
485
577
|
/** Total laid-out height of a run of blocks, paragraph spacing included. */
|
|
486
578
|
function blocksHeight(blocks) {
|
|
487
|
-
return blocks.reduce((sum, b) =>
|
|
579
|
+
return blocks.reduce((sum, b) => {
|
|
580
|
+
if (b.kind !== "paragraph" && b.kind !== "table" && isOutOfFlowFloat(b.float)) return sum;
|
|
581
|
+
return sum + (b.kind === "paragraph" ? b.spacingBeforePt + b.heightPt + b.spacingAfterPt : b.heightPt);
|
|
582
|
+
}, 0);
|
|
488
583
|
}
|
|
489
|
-
function layoutFooterSet(section, headersFooters, options, fontResources, contentWidth, marginLeft, pageHeight, footerOffsetPt) {
|
|
584
|
+
function layoutFooterSet(section, headersFooters, options, fontResources, imageResources, contentWidth, marginLeft, pageHeight, footerOffsetPt, bandMargins, relativeBox) {
|
|
490
585
|
const band = (type) => {
|
|
491
586
|
const ref = refByType(section.footers, type);
|
|
492
587
|
if (!ref) return { commands: [] };
|
|
493
588
|
const content = headersFooters.get(ref.relationshipId);
|
|
494
589
|
if (!content) return { commands: [] };
|
|
495
590
|
const render = (c) => {
|
|
496
|
-
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth);
|
|
497
|
-
return markPagination(drawBlocksSequentially(blocks, marginLeft, footerOffsetPt + blocks
|
|
591
|
+
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth, imageResources, relativeBox);
|
|
592
|
+
return markPagination(drawBlocksSequentially(blocks, marginLeft, footerOffsetPt + blocksHeight(blocks), pageHeight, contentWidth, void 0, bandMargins));
|
|
498
593
|
};
|
|
499
594
|
if (contentHasPageFields(content)) return {
|
|
500
595
|
commands: [],
|
|
@@ -530,7 +625,7 @@ function substitutePageFields(content, pageNumber, totalPages) {
|
|
|
530
625
|
}
|
|
531
626
|
function pickBand(set, band) {
|
|
532
627
|
const has = (e) => e.commands.length > 0 || e.renderDynamic !== void 0;
|
|
533
|
-
if (band === "first") return
|
|
628
|
+
if (band === "first") return set.first;
|
|
534
629
|
if (band === "even") return has(set.even) ? set.even : set.default;
|
|
535
630
|
return set.default;
|
|
536
631
|
}
|
|
@@ -539,24 +634,236 @@ function bandForPage(pageInSection, globalPageIdx, titlePg, evenAndOddHeaders) {
|
|
|
539
634
|
if (evenAndOddHeaders && (globalPageIdx + 1) % 2 === 0) return "even";
|
|
540
635
|
return "default";
|
|
541
636
|
}
|
|
542
|
-
|
|
543
|
-
|
|
637
|
+
/**
|
|
638
|
+
* §17.3.1.11 — the runs of consecutive paragraphs that carry the SAME frame.
|
|
639
|
+
* Word treats them as one floating text frame, so they lay out together in one
|
|
640
|
+
* box; the entries after the first become empty blocks, which keeps every
|
|
641
|
+
* caller's element indexing (and its section bounds) intact.
|
|
642
|
+
*
|
|
643
|
+
* @param elements The body elements, in order.
|
|
644
|
+
* @returns `starts`: the index each frame begins at → its paragraphs;
|
|
645
|
+
* `continued`: the indices swallowed by a frame that began earlier.
|
|
646
|
+
*/
|
|
647
|
+
function frameGroups(elements) {
|
|
648
|
+
const starts = /* @__PURE__ */ new Map();
|
|
649
|
+
const continued = /* @__PURE__ */ new Set();
|
|
650
|
+
const key = (p) => p.properties.frame ? JSON.stringify(p.properties.frame) : void 0;
|
|
651
|
+
let i = 0;
|
|
652
|
+
while (i < elements.length) {
|
|
653
|
+
const el = elements[i];
|
|
654
|
+
const k = el.kind === "paragraph" ? key(el.paragraph) : void 0;
|
|
655
|
+
if (k === void 0) {
|
|
656
|
+
i++;
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
const members = [el.paragraph];
|
|
660
|
+
let j = i + 1;
|
|
661
|
+
while (j < elements.length) {
|
|
662
|
+
const next = elements[j];
|
|
663
|
+
if (next.kind !== "paragraph" || key(next.paragraph) !== k) break;
|
|
664
|
+
members.push(next.paragraph);
|
|
665
|
+
continued.add(j);
|
|
666
|
+
j++;
|
|
667
|
+
}
|
|
668
|
+
starts.set(i, members);
|
|
669
|
+
i = j;
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
starts,
|
|
673
|
+
continued
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
/** A block that occupies no space at all — the tail of a frame group. */
|
|
677
|
+
function emptyBlock(options) {
|
|
678
|
+
return {
|
|
679
|
+
kind: "paragraph",
|
|
680
|
+
resolved: resolveParagraphProperties({}, options.styles),
|
|
681
|
+
lines: [],
|
|
682
|
+
heightPt: 0,
|
|
683
|
+
spacingBeforePt: 0,
|
|
684
|
+
spacingAfterPt: 0
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* A text frame as the floating box it is: the paragraphs inside it lay out at
|
|
689
|
+
* the frame's width, the box takes the height the frame states or the text
|
|
690
|
+
* needs, and the body flows past it as its `w:wrap` says.
|
|
691
|
+
*
|
|
692
|
+
* @param paragraphs The frame's paragraphs, in order.
|
|
693
|
+
* @param options Render options.
|
|
694
|
+
* @param fontResources Fonts by variant.
|
|
695
|
+
* @param imageResources Images by resource id.
|
|
696
|
+
* @param contentWidth The width available where the frame is anchored.
|
|
697
|
+
* @returns The laid-out frame.
|
|
698
|
+
*/
|
|
699
|
+
function layoutFrameBlock(paragraphs, options, fontResources, imageResources, contentWidth) {
|
|
700
|
+
const frame = paragraphs[0].properties.frame;
|
|
701
|
+
const exact = frame.heightRule === "exact" && frame.heightPt !== void 0;
|
|
702
|
+
const raw = (widthPt, pin) => layoutShapeBlock(frameShape(frame, paragraphs, widthPt, pin), options, fontResources, imageResources, contentWidth);
|
|
703
|
+
const atLeast = !exact && frame.heightPt !== void 0 && frame.heightRule !== "auto" ? frame.heightPt : 0;
|
|
704
|
+
const build = (widthPt) => {
|
|
705
|
+
const laid = raw(widthPt, exact);
|
|
706
|
+
return laid.kind === "shape" && atLeast > laid.heightPt ? raw(widthPt, true) : laid;
|
|
707
|
+
};
|
|
708
|
+
if (frame.widthPt !== void 0) return build(frame.widthPt);
|
|
709
|
+
const measured = build(contentWidth);
|
|
710
|
+
const natural = measured.kind === "shape" ? Math.max(1, ...measured.textLines.map((l) => l.contentWidthPt)) : contentWidth;
|
|
711
|
+
return build(Math.min(contentWidth, natural));
|
|
712
|
+
}
|
|
713
|
+
var MAX_FIT_PASSES = 4;
|
|
714
|
+
function scaleTextBody(text, k) {
|
|
715
|
+
return {
|
|
716
|
+
...text,
|
|
717
|
+
content: text.content.map((el) => el.kind === "paragraph" ? {
|
|
718
|
+
...el,
|
|
719
|
+
paragraph: {
|
|
720
|
+
...el.paragraph,
|
|
721
|
+
runs: el.paragraph.runs.map((run) => ({
|
|
722
|
+
...run,
|
|
723
|
+
properties: {
|
|
724
|
+
...run.properties,
|
|
725
|
+
fontSizePt: pt((run.properties.fontSizePt ?? 11) * k)
|
|
726
|
+
}
|
|
727
|
+
}))
|
|
728
|
+
}
|
|
729
|
+
} : el)
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function frameShape(frame, paragraphs, widthPt, exact) {
|
|
733
|
+
const lead = paragraphs[0]?.properties;
|
|
734
|
+
const edge = lead?.borders?.top ?? lead?.borders?.left;
|
|
735
|
+
const vertical = lead?.textDirection === "btLr" ? "vert270" : lead?.textDirection === "tbRl" ? "vert" : void 0;
|
|
736
|
+
const painted = lead?.shading !== void 0 || edge !== void 0 && edge.style !== "none";
|
|
737
|
+
return {
|
|
738
|
+
width: pt(widthPt),
|
|
739
|
+
height: pt(exact ? frame.heightPt ?? 0 : 0),
|
|
740
|
+
geometry: painted ? {
|
|
741
|
+
kind: "preset",
|
|
742
|
+
preset: "rect",
|
|
743
|
+
adjust: /* @__PURE__ */ new Map()
|
|
744
|
+
} : {
|
|
745
|
+
kind: "custom",
|
|
746
|
+
custom: {
|
|
747
|
+
pathWidth: 0,
|
|
748
|
+
pathHeight: 0,
|
|
749
|
+
commands: []
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
fill: lead?.shading ? {
|
|
753
|
+
kind: "solid",
|
|
754
|
+
colorHex: lead.shading.colorHex
|
|
755
|
+
} : { kind: "none" },
|
|
756
|
+
...edge && edge.style !== "none" ? { line: {
|
|
757
|
+
fill: "solid",
|
|
758
|
+
colorHex: edge.colorHex ?? "000000",
|
|
759
|
+
width: edge.width ?? pt(.75)
|
|
760
|
+
} } : {},
|
|
761
|
+
float: frameFloat(frame),
|
|
762
|
+
paragraphProperties: {},
|
|
763
|
+
text: {
|
|
764
|
+
content: paragraphs.map((paragraph) => ({
|
|
765
|
+
kind: "paragraph",
|
|
766
|
+
paragraph
|
|
767
|
+
})),
|
|
768
|
+
insetLeft: pt(0),
|
|
769
|
+
insetRight: pt(0),
|
|
770
|
+
insetTop: pt(0),
|
|
771
|
+
insetBottom: pt(0),
|
|
772
|
+
...vertical ? { vertical } : {},
|
|
773
|
+
...exact ? {} : { autoFit: true }
|
|
774
|
+
}
|
|
775
|
+
};
|
|
544
776
|
}
|
|
545
|
-
|
|
777
|
+
/** §17.3.1.11 → §20.4.2.3: the frame's anchor, in the terms floats are placed in. */
|
|
778
|
+
function frameFloat(frame) {
|
|
779
|
+
const wrap = frame.wrap === "none" ? "none" : frame.wrap === "notBeside" ? "topAndBottom" : "square";
|
|
780
|
+
const hRel = frame.hAnchor === "page" ? "page" : frame.hAnchor === "margin" ? "margin" : "column";
|
|
781
|
+
const vRel = frame.vAnchor === "page" ? "page" : frame.vAnchor === "margin" ? "margin" : "paragraph";
|
|
782
|
+
const align = frame.xAlign === "left" || frame.xAlign === "center" || frame.xAlign === "right" ? frame.xAlign : void 0;
|
|
783
|
+
const vAlign = frame.yAlign === "top" || frame.yAlign === "center" || frame.yAlign === "bottom" ? frame.yAlign : void 0;
|
|
784
|
+
return {
|
|
785
|
+
wrap,
|
|
786
|
+
posH: {
|
|
787
|
+
relativeFrom: hRel,
|
|
788
|
+
...align ? { align } : { offsetPt: frame.xPt ?? pt(0) }
|
|
789
|
+
},
|
|
790
|
+
posV: {
|
|
791
|
+
relativeFrom: vRel,
|
|
792
|
+
...vAlign ? { align: vAlign } : { offsetPt: frame.yPt ?? pt(0) }
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* A drawing's width when it states one as a share of the page or the margins.
|
|
798
|
+
*
|
|
799
|
+
* @param rel The relative size, if any.
|
|
800
|
+
* @param content The margin box's width.
|
|
801
|
+
* @param box The page box, when the caller knows it.
|
|
802
|
+
* @returns The width in points, or `undefined` when none is stated.
|
|
803
|
+
*/
|
|
804
|
+
function relativeWidth(rel, content, box) {
|
|
805
|
+
if (!rel?.widthPct) return void 0;
|
|
806
|
+
const from = rel.widthFrom;
|
|
807
|
+
return (box === void 0 || from === void 0 || from === "margin" ? content : from === "page" ? box.pageWidthPt : from === "leftMargin" ? box.marginLeftPt : box.marginRightPt) * rel.widthPct;
|
|
808
|
+
}
|
|
809
|
+
/** The height twin of {@link relativeWidth}; the margin box's height is the page's content height. */
|
|
810
|
+
function relativeHeight(rel, contentHeight, box) {
|
|
811
|
+
if (!rel?.heightPct) return void 0;
|
|
812
|
+
const from = rel.heightFrom;
|
|
813
|
+
const base = box === void 0 || from === void 0 || from === "margin" ? contentHeight : from === "page" ? box.pageHeightPt : from === "topMargin" ? box.marginTopPt : box.marginBottomPt;
|
|
814
|
+
return base === void 0 ? void 0 : base * rel.heightPct;
|
|
815
|
+
}
|
|
816
|
+
function laidOutBlocksFor(elements, options, fontResources, contentWidth, imageResources, box) {
|
|
817
|
+
const frames = frameGroups(elements);
|
|
818
|
+
return elements.map((el, idx) => {
|
|
819
|
+
const group = frames.starts.get(idx);
|
|
820
|
+
if (group) return layoutFrameBlock(group, options, fontResources, imageResources, contentWidth);
|
|
821
|
+
if (frames.continued.has(idx)) return emptyBlock(options);
|
|
822
|
+
return layoutBodyElement(el, options, fontResources, imageResources, contentWidth, void 0, box);
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
function layoutBodyElement(el, options, fontResources, imageResources, contentWidth, maxHeight, box) {
|
|
546
826
|
if (el.kind === "paragraph") return layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, contentWidth);
|
|
547
827
|
if (el.kind === "table") return layoutTableBlock(el.table, options, fontResources, imageResources, contentWidth);
|
|
548
|
-
if (el.kind === "image") return layoutImageBlock(el.image, imageResources, contentWidth);
|
|
828
|
+
if (el.kind === "image") return layoutImageBlock(el.image, imageResources, contentWidth, maxHeight, box);
|
|
549
829
|
if (el.kind === "chart") return layoutChartBlock(el.chart, options, fontResources, contentWidth, maxHeight);
|
|
550
|
-
return layoutShapeBlock(el.shape, options, fontResources, imageResources, contentWidth, maxHeight);
|
|
830
|
+
return layoutShapeBlock(el.shape, options, fontResources, imageResources, contentWidth, maxHeight, box);
|
|
551
831
|
}
|
|
552
|
-
function layoutImageBlock(image, imageResources, contentWidth) {
|
|
553
|
-
let widthPt = image.width;
|
|
554
|
-
let heightPt = image.height;
|
|
555
|
-
if (widthPt > contentWidth) {
|
|
832
|
+
function layoutImageBlock(image, imageResources, contentWidth, maxHeight, box) {
|
|
833
|
+
let widthPt = relativeWidth(image.relativeSize, contentWidth, box) ?? image.width;
|
|
834
|
+
let heightPt = relativeHeight(image.relativeSize, maxHeight, box) ?? (image.relativeSize?.widthPct && image.width > 0 ? image.height * (widthPt / image.width) : image.height);
|
|
835
|
+
if (widthPt > contentWidth && !image.float) {
|
|
556
836
|
const scale = contentWidth / widthPt;
|
|
557
837
|
widthPt = contentWidth;
|
|
558
838
|
heightPt = heightPt * scale;
|
|
559
839
|
}
|
|
840
|
+
let drawInset;
|
|
841
|
+
const eff = image.effectExtent;
|
|
842
|
+
if (eff) {
|
|
843
|
+
const s = image.width > 0 ? widthPt / image.width : 1;
|
|
844
|
+
let box = {
|
|
845
|
+
dxPt: eff.leftPt * s,
|
|
846
|
+
dyTopPt: eff.topPt * s,
|
|
847
|
+
widthPt,
|
|
848
|
+
heightPt
|
|
849
|
+
};
|
|
850
|
+
let reservedW = widthPt + (eff.leftPt + eff.rightPt) * s;
|
|
851
|
+
let reservedH = heightPt + (eff.topPt + eff.bottomPt) * s;
|
|
852
|
+
if (reservedW > contentWidth) {
|
|
853
|
+
const k = contentWidth / reservedW;
|
|
854
|
+
reservedW = contentWidth;
|
|
855
|
+
reservedH *= k;
|
|
856
|
+
box = {
|
|
857
|
+
dxPt: box.dxPt * k,
|
|
858
|
+
dyTopPt: box.dyTopPt * k,
|
|
859
|
+
widthPt: box.widthPt * k,
|
|
860
|
+
heightPt: box.heightPt * k
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
widthPt = reservedW;
|
|
864
|
+
heightPt = reservedH;
|
|
865
|
+
drawInset = box;
|
|
866
|
+
}
|
|
560
867
|
const res = image.resource ? imageResources?.get(image.resource) : void 0;
|
|
561
868
|
const resolvedAlignment = image.paragraphProperties.alignment ?? "left";
|
|
562
869
|
return {
|
|
@@ -565,27 +872,33 @@ function layoutImageBlock(image, imageResources, contentWidth) {
|
|
|
565
872
|
heightPt,
|
|
566
873
|
resolvedAlignment,
|
|
567
874
|
resourceName: res?.resourceName ?? "",
|
|
875
|
+
...image.outline ? { outline: image.outline } : {},
|
|
876
|
+
...image.crop ? { crop: image.crop } : {},
|
|
877
|
+
...image.rotation60k ? { rotationDeg: image.rotation60k / 6e4 } : {},
|
|
878
|
+
...image.flipH ? { flipH: true } : {},
|
|
879
|
+
...image.flipV ? { flipV: true } : {},
|
|
880
|
+
...drawInset ? { drawInset } : {},
|
|
568
881
|
spacingBeforePt: image.paragraphProperties.spacingBefore ?? 0,
|
|
569
882
|
spacingAfterPt: image.paragraphProperties.spacingAfter ?? 0,
|
|
570
883
|
...image.altText ? { altText: image.altText } : {},
|
|
571
884
|
...image.float ? { float: image.float } : {}
|
|
572
885
|
};
|
|
573
886
|
}
|
|
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) {
|
|
887
|
+
function layoutShapeBlock(shape, options, fontResources, imageResources, contentWidth, maxHeight, box, fitPass = 0) {
|
|
888
|
+
let widthPt = relativeWidth(shape.relativeSize, contentWidth, box) ?? shape.width;
|
|
889
|
+
let heightPt = relativeHeight(shape.relativeSize, maxHeight, box) ?? (shape.relativeSize?.widthPct && shape.width > 0 ? shape.height * (widthPt / shape.width) : shape.height);
|
|
890
|
+
if (!shape.float && widthPt > contentWidth && widthPt > 0) {
|
|
578
891
|
const scale = contentWidth / widthPt;
|
|
579
892
|
widthPt = contentWidth;
|
|
580
893
|
heightPt *= scale;
|
|
581
894
|
}
|
|
582
|
-
if (maxHeight !== void 0 && heightPt > maxHeight && heightPt > 0) {
|
|
895
|
+
if (!shape.float && maxHeight !== void 0 && heightPt > maxHeight && heightPt > 0) {
|
|
583
896
|
const scale = maxHeight / heightPt;
|
|
584
897
|
widthPt *= scale;
|
|
585
898
|
heightPt = maxHeight;
|
|
586
899
|
}
|
|
587
|
-
const paths = buildShapePaths(shape.geometry, widthPt, heightPt);
|
|
588
900
|
const fillGradient = shape.fill.kind === "gradient" ? shape.fill.gradient : void 0;
|
|
901
|
+
const fillImageResourceName = shape.fill.kind === "picture" && shape.fill.imageResource !== void 0 ? imageResources?.get(shape.fill.imageResource)?.resourceName : void 0;
|
|
589
902
|
const fillColorHex = shape.fill.kind === "solid" ? shape.fill.colorHex : fillGradient ? gradientToSolid(fillGradient) : void 0;
|
|
590
903
|
const stroke = buildStroke(shape.line);
|
|
591
904
|
const t = shape.transform;
|
|
@@ -596,33 +909,150 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
596
909
|
const insetTopPt = text?.insetTop ?? 3.6;
|
|
597
910
|
const insetBottomPt = text?.insetBottom ?? 3.6;
|
|
598
911
|
const textLines = [];
|
|
912
|
+
const textCharts = /* @__PURE__ */ new Map();
|
|
913
|
+
const textShapes = /* @__PURE__ */ new Map();
|
|
914
|
+
const textTables = /* @__PURE__ */ new Map();
|
|
915
|
+
const textLineGaps = /* @__PURE__ */ new Map();
|
|
599
916
|
let textHeightPt = 0;
|
|
917
|
+
const vertical = text?.vertical;
|
|
600
918
|
if (text && text.content.length > 0) {
|
|
601
|
-
const innerWidth = Math.max(1, widthPt - insetLeftPt - insetRightPt);
|
|
919
|
+
const innerWidth = vertical ? Math.max(1, heightPt - insetTopPt - insetBottomPt) : Math.max(1, widthPt - insetLeftPt - insetRightPt);
|
|
602
920
|
for (const el of text.content) {
|
|
603
|
-
if (el.kind
|
|
921
|
+
if (el.kind === "image") {
|
|
922
|
+
const res = el.image.resource ? imageResources?.get(el.image.resource) : void 0;
|
|
923
|
+
const scaled = Math.min(1, innerWidth / Math.max(1, el.image.width));
|
|
924
|
+
const line = {
|
|
925
|
+
tokens: [{
|
|
926
|
+
kind: "image",
|
|
927
|
+
imageResourceName: res?.resourceName ?? "",
|
|
928
|
+
widthPt: el.image.width * scaled,
|
|
929
|
+
heightPt: el.image.height * scaled,
|
|
930
|
+
isSpace: false,
|
|
931
|
+
bidiLevel: 0
|
|
932
|
+
}],
|
|
933
|
+
contentWidthPt: el.image.width * scaled,
|
|
934
|
+
maxFontSizePt: el.image.height * scaled,
|
|
935
|
+
availableWidthPt: innerWidth,
|
|
936
|
+
firstLine: true,
|
|
937
|
+
resolved: resolveParagraphProperties(el.image.paragraphProperties, options.styles),
|
|
938
|
+
isLastInParagraph: true
|
|
939
|
+
};
|
|
940
|
+
textLines.push(line);
|
|
941
|
+
textHeightPt += computeLineHeight(line, line.resolved);
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (el.kind === "chart") {
|
|
945
|
+
const laid = layoutChartBlock(el.chart, options, fontResources, innerWidth);
|
|
946
|
+
const line = {
|
|
947
|
+
tokens: [],
|
|
948
|
+
contentWidthPt: laid.widthPt,
|
|
949
|
+
maxFontSizePt: laid.heightPt,
|
|
950
|
+
availableWidthPt: innerWidth,
|
|
951
|
+
firstLine: true,
|
|
952
|
+
resolved: resolveParagraphProperties(el.chart.paragraphProperties, options.styles),
|
|
953
|
+
isLastInParagraph: true
|
|
954
|
+
};
|
|
955
|
+
textCharts.set(textLines.length, laid);
|
|
956
|
+
textLines.push(line);
|
|
957
|
+
textHeightPt += computeLineHeight(line, line.resolved);
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
if (el.kind === "shape") {
|
|
961
|
+
const laid = layoutShapeBlock(el.shape, options, fontResources, imageResources, innerWidth);
|
|
962
|
+
const line = {
|
|
963
|
+
tokens: [],
|
|
964
|
+
contentWidthPt: laid.widthPt,
|
|
965
|
+
maxFontSizePt: laid.heightPt,
|
|
966
|
+
availableWidthPt: innerWidth,
|
|
967
|
+
firstLine: true,
|
|
968
|
+
resolved: resolveParagraphProperties(el.shape.paragraphProperties, options.styles),
|
|
969
|
+
isLastInParagraph: true
|
|
970
|
+
};
|
|
971
|
+
textShapes.set(textLines.length, laid);
|
|
972
|
+
textLines.push(line);
|
|
973
|
+
textHeightPt += computeLineHeight(line, line.resolved);
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
if (el.kind === "table") {
|
|
977
|
+
const laid = layoutTableBlock(el.table, options, fontResources, imageResources, innerWidth);
|
|
978
|
+
const line = {
|
|
979
|
+
tokens: [],
|
|
980
|
+
contentWidthPt: laid.totalWidthPt,
|
|
981
|
+
maxFontSizePt: laid.heightPt,
|
|
982
|
+
metricHeightPt: laid.heightPt,
|
|
983
|
+
availableWidthPt: innerWidth,
|
|
984
|
+
firstLine: true,
|
|
985
|
+
resolved: resolveParagraphProperties({}, options.styles),
|
|
986
|
+
isLastInParagraph: true
|
|
987
|
+
};
|
|
988
|
+
textTables.set(textLines.length, laid);
|
|
989
|
+
textLines.push(line);
|
|
990
|
+
textHeightPt += computeLineHeight(line, line.resolved);
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
604
993
|
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) {
|
|
994
|
+
const before = textLines.length > 0 ? blk.spacingBeforePt : 0;
|
|
995
|
+
if (before > 0) {
|
|
996
|
+
const last = textLines.length - 1;
|
|
997
|
+
textLineGaps.set(last, (textLineGaps.get(last) ?? 0) + before);
|
|
998
|
+
textHeightPt += before;
|
|
999
|
+
}
|
|
1000
|
+
for (const line of blk.lines) {
|
|
615
1001
|
textLines.push(line);
|
|
616
1002
|
textHeightPt += computeLineHeight(line, blk.resolved);
|
|
617
1003
|
}
|
|
1004
|
+
if (blk.spacingAfterPt > 0) {
|
|
1005
|
+
const last = textLines.length - 1;
|
|
1006
|
+
textLineGaps.set(last, (textLineGaps.get(last) ?? 0) + blk.spacingAfterPt);
|
|
1007
|
+
}
|
|
618
1008
|
textHeightPt += blk.spacingAfterPt;
|
|
619
1009
|
}
|
|
620
1010
|
}
|
|
1011
|
+
if (text?.autoFit && !vertical && textLines.length > 0) heightPt = textHeightPt + insetTopPt + insetBottomPt;
|
|
1012
|
+
if (text?.fitToBox === true && textLines.length > 0 && fitPass < MAX_FIT_PASSES) {
|
|
1013
|
+
const innerW = Math.max(1, widthPt - insetLeftPt - insetRightPt);
|
|
1014
|
+
const innerH = Math.max(1, heightPt - insetTopPt - insetBottomPt);
|
|
1015
|
+
let widest = 0;
|
|
1016
|
+
let running = 0;
|
|
1017
|
+
let paragraphs = 0;
|
|
1018
|
+
for (const l of textLines) {
|
|
1019
|
+
running += l.contentWidthPt;
|
|
1020
|
+
if (l.isLastInParagraph) {
|
|
1021
|
+
widest = Math.max(widest, running);
|
|
1022
|
+
running = 0;
|
|
1023
|
+
paragraphs++;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
widest = Math.max(1, widest, running);
|
|
1027
|
+
const perLine = textHeightPt / Math.max(1, textLines.length);
|
|
1028
|
+
const naturalHeight = Math.max(1, perLine * Math.max(1, paragraphs || textLines.length));
|
|
1029
|
+
const k = Math.min(innerW / widest, innerH / naturalHeight);
|
|
1030
|
+
if (Number.isFinite(k) && k > .05 && k < 20 && Math.abs(k - 1) > .02) return layoutShapeBlock({
|
|
1031
|
+
...shape,
|
|
1032
|
+
text: scaleTextBody(text, k)
|
|
1033
|
+
}, options, fontResources, imageResources, contentWidth, maxHeight, box, fitPass + 1);
|
|
1034
|
+
}
|
|
1035
|
+
const paths = buildShapePaths(shape.geometry, widthPt, heightPt);
|
|
1036
|
+
const markerPaths = lineEndPaths(paths, shape.line, stroke?.widthPt ?? 0);
|
|
1037
|
+
const scale = shape.width > 0 ? widthPt / shape.width : 1;
|
|
1038
|
+
const children = (shape.children ?? []).map((child) => ({
|
|
1039
|
+
laid: layoutShapeBlock({
|
|
1040
|
+
...child.shape,
|
|
1041
|
+
width: pt(child.shape.width * scale),
|
|
1042
|
+
height: pt(child.shape.height * scale)
|
|
1043
|
+
}, options, fontResources, imageResources, Number.POSITIVE_INFINITY),
|
|
1044
|
+
xPt: child.xPt * scale,
|
|
1045
|
+
yPt: child.yPt * scale
|
|
1046
|
+
}));
|
|
621
1047
|
return {
|
|
622
1048
|
kind: "shape",
|
|
623
1049
|
widthPt,
|
|
624
1050
|
heightPt,
|
|
1051
|
+
...children.length > 0 ? { children } : {},
|
|
625
1052
|
paths,
|
|
1053
|
+
...markerPaths.length > 0 ? { markerPaths } : {},
|
|
1054
|
+
...fillImageResourceName ? { fillImageResourceName } : {},
|
|
1055
|
+
...fillImageResourceName && shape.fill.imageCrop ? { fillImageCrop: shape.fill.imageCrop } : {},
|
|
626
1056
|
...fillColorHex ? { fillColorHex } : {},
|
|
627
1057
|
...fillGradient ? { fillGradient } : {},
|
|
628
1058
|
...stroke ? { stroke } : {},
|
|
@@ -634,12 +1064,17 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
634
1064
|
spacingBeforePt: pp.spacingBefore ?? 0,
|
|
635
1065
|
spacingAfterPt: pp.spacingAfter ?? 0,
|
|
636
1066
|
textLines,
|
|
1067
|
+
...textCharts.size > 0 ? { textCharts } : {},
|
|
1068
|
+
...textShapes.size > 0 ? { textShapes } : {},
|
|
1069
|
+
...textTables.size > 0 ? { textTables } : {},
|
|
1070
|
+
...textLineGaps.size > 0 ? { textLineGaps } : {},
|
|
637
1071
|
textHeightPt,
|
|
638
1072
|
insetLeftPt,
|
|
639
1073
|
insetRightPt,
|
|
640
1074
|
insetTopPt,
|
|
641
1075
|
insetBottomPt,
|
|
642
1076
|
anchor: text?.anchor ?? "t",
|
|
1077
|
+
...vertical ? { vertical } : {},
|
|
643
1078
|
...shape.altText ? { altText: shape.altText } : {},
|
|
644
1079
|
...shape.float ? { float: shape.float } : {}
|
|
645
1080
|
};
|
|
@@ -693,6 +1128,7 @@ function buildChartLayout(chart, widthPt, heightPt, font) {
|
|
|
693
1128
|
};
|
|
694
1129
|
const shapes = [];
|
|
695
1130
|
if (scene.background) shapes.push(rectPrim(scene.background));
|
|
1131
|
+
if (scene.plotBackground) shapes.push(rectPrim(scene.plotBackground));
|
|
696
1132
|
for (const g of scene.gridlines ?? []) shapes.push(polylinePrim(g));
|
|
697
1133
|
for (const pg of scene.polygons ?? []) shapes.push(polygonPrim(pg));
|
|
698
1134
|
for (const r of scene.rects) shapes.push(rectPrim(r));
|
|
@@ -818,14 +1254,20 @@ function circlePath(cx, cy, r) {
|
|
|
818
1254
|
const k = .5522847498307936 * r;
|
|
819
1255
|
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
1256
|
}
|
|
1257
|
+
function strokeOfRect(r) {
|
|
1258
|
+
if (!r.strokeHex) return {};
|
|
1259
|
+
const stroke = buildStroke({
|
|
1260
|
+
colorHex: r.strokeHex,
|
|
1261
|
+
width: pt(r.strokeWidthPt ?? 1),
|
|
1262
|
+
...r.strokeDash ? { dash: r.strokeDash } : {}
|
|
1263
|
+
});
|
|
1264
|
+
return stroke ? { stroke } : {};
|
|
1265
|
+
}
|
|
821
1266
|
function rectPrim(r) {
|
|
822
1267
|
return {
|
|
823
1268
|
paths: [rectAtPath(r.x, r.y, r.w, r.h)],
|
|
824
1269
|
...r.fillHex ? { fillColorHex: r.fillHex } : {},
|
|
825
|
-
...r
|
|
826
|
-
colorHex: r.strokeHex,
|
|
827
|
-
widthPt: r.strokeWidthPt ?? 1
|
|
828
|
-
} } : {}
|
|
1270
|
+
...strokeOfRect(r)
|
|
829
1271
|
};
|
|
830
1272
|
}
|
|
831
1273
|
function polylinePrim(p) {
|
|
@@ -915,10 +1357,16 @@ function collectImageResources(body, headersFooters, options) {
|
|
|
915
1357
|
for (const run of el.paragraph.runs) if (run.inlineImage?.resource) seen.add(run.inlineImage.resource);
|
|
916
1358
|
} else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
|
|
917
1359
|
else if (el.kind === "shape") {
|
|
918
|
-
|
|
1360
|
+
const shapeImages = (sh) => {
|
|
1361
|
+
if (sh.fill.imageResource) seen.add(sh.fill.imageResource);
|
|
1362
|
+
if (sh.text) visit(sh.text.content);
|
|
1363
|
+
for (const child of sh.children ?? []) shapeImages(child.shape);
|
|
1364
|
+
};
|
|
1365
|
+
shapeImages(el.shape);
|
|
919
1366
|
}
|
|
920
1367
|
};
|
|
921
1368
|
visit(body);
|
|
1369
|
+
if (options.pageBackgroundFill?.imageResource !== void 0) seen.add(options.pageBackgroundFill.imageResource);
|
|
922
1370
|
for (const hf of headersFooters.values()) visit(hf);
|
|
923
1371
|
for (const note of options.footnotes?.values() ?? []) visit(note);
|
|
924
1372
|
for (const note of options.endnotes?.values() ?? []) visit(note);
|
|
@@ -942,12 +1390,257 @@ function collectImageResources(body, headersFooters, options) {
|
|
|
942
1390
|
}
|
|
943
1391
|
return out;
|
|
944
1392
|
}
|
|
945
|
-
function
|
|
1393
|
+
function chartPageItems(laid, x, bottomYUp, pageHeight, structId) {
|
|
1394
|
+
const fig = structId !== void 0 ? { structId } : {};
|
|
1395
|
+
const out = [];
|
|
1396
|
+
for (const sh of laid.layout.shapes) out.push({
|
|
1397
|
+
type: "shape",
|
|
1398
|
+
shape: {
|
|
1399
|
+
paths: sh.paths,
|
|
1400
|
+
...sh.fillColorHex ? { fillColorHex: sh.fillColorHex } : {},
|
|
1401
|
+
...sh.stroke ? { stroke: sh.stroke } : {},
|
|
1402
|
+
transform: flipTransform([
|
|
1403
|
+
1,
|
|
1404
|
+
0,
|
|
1405
|
+
0,
|
|
1406
|
+
1,
|
|
1407
|
+
x,
|
|
1408
|
+
bottomYUp
|
|
1409
|
+
], pageHeight)
|
|
1410
|
+
},
|
|
1411
|
+
...fig
|
|
1412
|
+
});
|
|
1413
|
+
for (const t of laid.layout.texts) out.push({
|
|
1414
|
+
type: "line",
|
|
1415
|
+
line: t.line,
|
|
1416
|
+
originX: pt(x + t.x),
|
|
1417
|
+
baselineY: pt(pageHeight - (bottomYUp + t.y)),
|
|
1418
|
+
...t.rotationDeg ? { rotationDeg: t.rotationDeg } : {},
|
|
1419
|
+
...fig
|
|
1420
|
+
});
|
|
1421
|
+
return out;
|
|
1422
|
+
}
|
|
1423
|
+
function emitShapeText(sh, x, bottomYUp, sink, pageHeight, figId) {
|
|
1424
|
+
if (sh.textLines.length === 0) return;
|
|
1425
|
+
if (sh.vertical) {
|
|
1426
|
+
const up = sh.vertical === "vert270";
|
|
1427
|
+
const innerHeight = Math.max(1, sh.heightPt - sh.insetTopPt - sh.insetBottomPt);
|
|
1428
|
+
let across = sh.anchor === "b" ? sh.widthPt - sh.insetRightPt - sh.textHeightPt : sh.anchor === "ctr" ? (sh.widthPt - sh.textHeightPt) / 2 : sh.insetLeftPt;
|
|
1429
|
+
for (const line of sh.textLines) {
|
|
1430
|
+
const h = computeLineHeight(line, line.resolved);
|
|
1431
|
+
const lineOffset = alignmentOffset(line.resolved.alignment, line.contentWidthPt, innerHeight);
|
|
1432
|
+
const off = lineBaselineOffset(line, line.resolved);
|
|
1433
|
+
const baselineAcross = up ? across + h - off : across + off;
|
|
1434
|
+
sink.push({
|
|
1435
|
+
type: "line",
|
|
1436
|
+
line,
|
|
1437
|
+
originX: pt(up ? x + baselineAcross : x + sh.widthPt - baselineAcross),
|
|
1438
|
+
baselineY: pt(pageHeight - (up ? bottomYUp + sh.insetBottomPt + lineOffset : bottomYUp + sh.heightPt - sh.insetTopPt - lineOffset)),
|
|
1439
|
+
rotationDeg: up ? 90 : -90,
|
|
1440
|
+
...figId !== void 0 ? { structId: figId } : {}
|
|
1441
|
+
});
|
|
1442
|
+
across += h;
|
|
1443
|
+
}
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
const shapeTop = bottomYUp + sh.heightPt;
|
|
1447
|
+
const innerWidth = Math.max(1, sh.widthPt - sh.insetLeftPt - sh.insetRightPt);
|
|
1448
|
+
let textY;
|
|
1449
|
+
if (sh.anchor === "b") textY = bottomYUp + sh.insetBottomPt + sh.textHeightPt;
|
|
1450
|
+
else if (sh.anchor === "ctr") textY = bottomYUp + (sh.heightPt + sh.textHeightPt) / 2;
|
|
1451
|
+
else textY = shapeTop - sh.insetTopPt;
|
|
1452
|
+
sh.textLines.forEach((line, i) => {
|
|
1453
|
+
textY -= computeLineHeight(line, line.resolved);
|
|
1454
|
+
const lineOffset = alignmentOffset(line.resolved.alignment, line.contentWidthPt, innerWidth);
|
|
1455
|
+
const nested = sh.textCharts?.get(i);
|
|
1456
|
+
if (nested) {
|
|
1457
|
+
sink.push(...chartPageItems(nested, x + sh.insetLeftPt + lineOffset, textY, pageHeight, figId));
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
const drawing = sh.textShapes?.get(i);
|
|
1461
|
+
if (drawing) {
|
|
1462
|
+
emitShapeItems(drawing, x + sh.insetLeftPt + lineOffset, textY, sink, pageHeight, figId);
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
const table = sh.textTables?.get(i);
|
|
1466
|
+
if (table) {
|
|
1467
|
+
let rowY = textY + table.heightPt;
|
|
1468
|
+
for (const row of table.rows) {
|
|
1469
|
+
emitRowChunk(sink, row, x + sh.insetLeftPt + lineOffset + table.xOffsetPt, rowY, pageHeight, table.colCount);
|
|
1470
|
+
rowY -= row.heightPt;
|
|
1471
|
+
}
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
sink.push({
|
|
1475
|
+
type: "line",
|
|
1476
|
+
line,
|
|
1477
|
+
originX: pt(x + sh.insetLeftPt + lineOffset),
|
|
1478
|
+
baselineY: pt(pageHeight - (textY + lineBaselineOffset(line, line.resolved))),
|
|
1479
|
+
...figId !== void 0 ? { structId: figId } : {}
|
|
1480
|
+
});
|
|
1481
|
+
textY -= sh.textLineGaps?.get(i) ?? 0;
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
function emitShapeItems(sh, x, bottomYUp, sink, pageHeight, figId) {
|
|
1485
|
+
if (sh.fillImageResourceName) sink.push({
|
|
1486
|
+
type: "image",
|
|
1487
|
+
x: pt(x),
|
|
1488
|
+
y: pt(pageHeight - (bottomYUp + sh.heightPt)),
|
|
1489
|
+
width: pt(sh.widthPt),
|
|
1490
|
+
height: pt(sh.heightPt),
|
|
1491
|
+
imageResourceName: sh.fillImageResourceName,
|
|
1492
|
+
...sh.fillImageCrop ? { crop: sh.fillImageCrop } : {},
|
|
1493
|
+
clip: {
|
|
1494
|
+
paths: sh.paths,
|
|
1495
|
+
transform: flipTransform(buildShapeTransform(x, bottomYUp, sh.widthPt, sh.heightPt, sh.rotation60k, sh.flipH, sh.flipV), pageHeight)
|
|
1496
|
+
},
|
|
1497
|
+
...figId !== void 0 ? { structId: figId } : {}
|
|
1498
|
+
});
|
|
1499
|
+
if (sh.markerPaths && sh.stroke) sink.push({
|
|
1500
|
+
type: "shape",
|
|
1501
|
+
shape: {
|
|
1502
|
+
paths: sh.markerPaths,
|
|
1503
|
+
fillColorHex: sh.stroke.colorHex,
|
|
1504
|
+
transform: flipTransform(buildShapeTransform(x, bottomYUp, sh.widthPt, sh.heightPt, sh.rotation60k, sh.flipH, sh.flipV), pageHeight)
|
|
1505
|
+
},
|
|
1506
|
+
...figId !== void 0 ? { structId: figId } : {}
|
|
1507
|
+
});
|
|
1508
|
+
if (sh.paths.some((path) => path.segments.length > 0)) sink.push({
|
|
1509
|
+
type: "shape",
|
|
1510
|
+
shape: {
|
|
1511
|
+
paths: sh.paths,
|
|
1512
|
+
...sh.fillColorHex ? { fillColorHex: sh.fillColorHex } : {},
|
|
1513
|
+
...sh.fillGradient ? { fillGradient: sh.fillGradient } : {},
|
|
1514
|
+
...sh.stroke ? { stroke: sh.stroke } : {},
|
|
1515
|
+
...sh.shadow ? { shadow: sh.shadow } : {},
|
|
1516
|
+
transform: flipTransform(buildShapeTransform(x, bottomYUp, sh.widthPt, sh.heightPt, sh.rotation60k, sh.flipH, sh.flipV), pageHeight)
|
|
1517
|
+
},
|
|
1518
|
+
...figId !== void 0 ? { structId: figId } : {}
|
|
1519
|
+
});
|
|
1520
|
+
for (const child of sh.children ?? []) emitShapeItems(child.laid, x + child.xPt, bottomYUp + sh.heightPt - child.yPt - child.laid.heightPt, sink, pageHeight, figId);
|
|
1521
|
+
emitShapeText(sh, x, bottomYUp, sink, pageHeight, figId);
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* §20.1.2.2.24 — the four rules of a picture's own frame, around the box the
|
|
1525
|
+
* picture is drawn in. Borders paint after images and before shapes, so the
|
|
1526
|
+
* frame lands on top of the picture the way Word draws it.
|
|
1527
|
+
*
|
|
1528
|
+
* @param outline The picture's frame.
|
|
1529
|
+
* @param x Left edge of the picture box.
|
|
1530
|
+
* @param topY Distance from the page TOP down to the box's top edge.
|
|
1531
|
+
* @param widthPt Box width.
|
|
1532
|
+
* @param heightPt Box height.
|
|
1533
|
+
* @param structId Tagged-PDF structure node the picture belongs to.
|
|
1534
|
+
* @returns One {@link BorderItem} per side.
|
|
1535
|
+
*/
|
|
1536
|
+
function pictureOutlineItems(outline, x, topY, widthPt, heightPt, structId) {
|
|
1537
|
+
return [
|
|
1538
|
+
"top",
|
|
1539
|
+
"bottom",
|
|
1540
|
+
"left",
|
|
1541
|
+
"right"
|
|
1542
|
+
].map((side) => ({
|
|
1543
|
+
type: "border",
|
|
1544
|
+
side,
|
|
1545
|
+
x: pt(x),
|
|
1546
|
+
y: pt(topY),
|
|
1547
|
+
width: pt(widthPt),
|
|
1548
|
+
height: pt(heightPt),
|
|
1549
|
+
borderSizePt: outline.widthPt,
|
|
1550
|
+
borderColorHex: outline.colorHex,
|
|
1551
|
+
...structId !== void 0 ? { structId } : {}
|
|
1552
|
+
}));
|
|
1553
|
+
}
|
|
1554
|
+
function bandAnchor(float, widthPt, contentWidth, bandLeftPt = 0) {
|
|
1555
|
+
if (!float) return void 0;
|
|
1556
|
+
const align = float.posH?.align;
|
|
1557
|
+
if (align === "right") return contentWidth - widthPt;
|
|
1558
|
+
if (align === "center") return (contentWidth - widthPt) / 2;
|
|
1559
|
+
if (align === "left") return 0;
|
|
1560
|
+
const offset = float.posH?.offsetPt ?? 0;
|
|
1561
|
+
return float.posH?.relativeFrom === "page" ? offset - bandLeftPt : offset;
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* §20.4.2.3 — how far DOWN the page a band's anchored drawing starts, in the
|
|
1565
|
+
* band emitter's y-UP frame. A `page`-relative anchor is measured from the
|
|
1566
|
+
* paper's own top edge, whatever the band's cursor has reached: n773061.docx
|
|
1567
|
+
* hangs its header text box 92pt down the page and, measured from the header's
|
|
1568
|
+
* first paragraph, it landed 60pt lower than Word and LibreOffice put it.
|
|
1569
|
+
*
|
|
1570
|
+
* @param float The anchor.
|
|
1571
|
+
* @param cursorY The band's cursor (y-up), where a paragraph-relative
|
|
1572
|
+
* anchor is measured from.
|
|
1573
|
+
* @param pageHeight The page height.
|
|
1574
|
+
* @returns The drawing's top edge, y-up.
|
|
1575
|
+
*/
|
|
1576
|
+
function bandAnchorTop(float, cursorY, pageHeight, heightPt = 0, margins) {
|
|
1577
|
+
const align = float.posV?.align;
|
|
1578
|
+
if (align !== void 0) {
|
|
1579
|
+
const page = float.posV?.relativeFrom === "page" || !margins;
|
|
1580
|
+
const topYUp = page ? pageHeight : pageHeight - margins.top;
|
|
1581
|
+
const bottomYUp = page ? 0 : margins.bottom;
|
|
1582
|
+
if (align === "top") return topYUp;
|
|
1583
|
+
if (align === "bottom") return bottomYUp + heightPt;
|
|
1584
|
+
return bottomYUp + (topYUp - bottomYUp + heightPt) / 2;
|
|
1585
|
+
}
|
|
1586
|
+
const offset = float.posV?.offsetPt ?? 0;
|
|
1587
|
+
return float.posV?.relativeFrom === "page" ? pageHeight - offset : cursorY - offset;
|
|
1588
|
+
}
|
|
1589
|
+
function drawBlocksSequentially(blocks, startX, startY, pageHeight, contentWidth, structId, bandMargins) {
|
|
946
1590
|
const out = [];
|
|
947
1591
|
let cursorY = startY;
|
|
948
|
-
|
|
949
|
-
|
|
1592
|
+
const isBehind = (b) => "float" in b && b.float?.behind === true;
|
|
1593
|
+
const ordered = blocks.some(isBehind) ? [...blocks.filter(isBehind), ...blocks.filter((b) => !isBehind(b))] : blocks;
|
|
1594
|
+
for (const block of ordered) {
|
|
1595
|
+
if (block.kind === "table") {
|
|
1596
|
+
const tableX = startX + block.xOffsetPt;
|
|
1597
|
+
for (const row of block.rows) {
|
|
1598
|
+
emitRowChunk(out, row, tableX, cursorY, pageHeight, block.colCount);
|
|
1599
|
+
cursorY -= row.heightPt;
|
|
1600
|
+
}
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
if (block.kind === "chart") {
|
|
1604
|
+
cursorY -= block.spacingBeforePt + block.heightPt;
|
|
1605
|
+
const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
|
|
1606
|
+
out.push(...chartPageItems(block, startX + offset, cursorY, pageHeight, structId));
|
|
1607
|
+
cursorY -= block.spacingAfterPt;
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
if (block.kind === "image") {
|
|
1611
|
+
cursorY -= block.spacingBeforePt;
|
|
1612
|
+
const anchored = bandAnchor(block.float, block.widthPt, contentWidth, startX);
|
|
1613
|
+
const offset = anchored ?? alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
|
|
1614
|
+
const top = anchored === void 0 || !block.float ? cursorY : bandAnchorTop(block.float, cursorY, pageHeight, block.heightPt, bandMargins);
|
|
1615
|
+
out.push({
|
|
1616
|
+
type: "image",
|
|
1617
|
+
x: pt(startX + offset),
|
|
1618
|
+
y: pt(pageHeight - top),
|
|
1619
|
+
width: pt(block.widthPt),
|
|
1620
|
+
height: pt(block.heightPt),
|
|
1621
|
+
imageResourceName: block.resourceName,
|
|
1622
|
+
...block.crop ? { crop: block.crop } : {},
|
|
1623
|
+
...block.rotationDeg ? { rotationDeg: block.rotationDeg } : {},
|
|
1624
|
+
...block.flipH ? { flipH: true } : {},
|
|
1625
|
+
...block.flipV ? { flipV: true } : {}
|
|
1626
|
+
});
|
|
1627
|
+
if (block.outline) out.push(...pictureOutlineItems(block.outline, startX + offset, pageHeight - top, block.widthPt, block.heightPt, structId));
|
|
1628
|
+
if (anchored === void 0) cursorY -= block.heightPt;
|
|
1629
|
+
cursorY -= block.spacingAfterPt;
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
if (block.kind === "shape") {
|
|
1633
|
+
cursorY -= block.spacingBeforePt;
|
|
1634
|
+
const anchored = bandAnchor(block.float, block.widthPt, contentWidth, startX);
|
|
1635
|
+
const offset = anchored ?? alignmentOffset(block.resolvedAlignment, block.widthPt, contentWidth);
|
|
1636
|
+
const top = anchored === void 0 || !block.float ? cursorY : bandAnchorTop(block.float, cursorY, pageHeight, block.heightPt, bandMargins);
|
|
1637
|
+
emitShapeItems(block, startX + offset, top - block.heightPt, out, pageHeight, structId);
|
|
1638
|
+
if (anchored === void 0) cursorY -= block.heightPt;
|
|
1639
|
+
cursorY -= block.spacingAfterPt;
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
950
1642
|
cursorY -= block.spacingBeforePt;
|
|
1643
|
+
const bandTopY = cursorY;
|
|
951
1644
|
for (const line of block.lines) {
|
|
952
1645
|
const h = computeLineHeight(line, block.resolved);
|
|
953
1646
|
cursorY -= h;
|
|
@@ -957,14 +1650,60 @@ function drawBlocksSequentially(blocks, startX, startY, pageHeight, structId) {
|
|
|
957
1650
|
type: "line",
|
|
958
1651
|
line,
|
|
959
1652
|
originX: pt(startX + indentLeft + offset),
|
|
960
|
-
baselineY: pt(pageHeight - (cursorY +
|
|
1653
|
+
baselineY: pt(pageHeight - (cursorY + lineBaselineOffset(line, block.resolved))),
|
|
961
1654
|
...structId !== void 0 ? { structId } : {}
|
|
962
1655
|
});
|
|
963
1656
|
}
|
|
1657
|
+
out.push(...paragraphDecoration(block.resolved, startX + block.resolved.indentLeft, startX + contentWidth - block.resolved.indentRight, bandTopY, cursorY, pageHeight));
|
|
964
1658
|
cursorY -= block.spacingAfterPt;
|
|
965
1659
|
}
|
|
966
1660
|
return out;
|
|
967
1661
|
}
|
|
1662
|
+
/**
|
|
1663
|
+
* §17.3.1.31 `w:shd` and §17.3.1.24 `w:pBdr` — the band behind a paragraph and
|
|
1664
|
+
* the rules around it, for a paragraph occupying `topY`…`bottomY` (both in the
|
|
1665
|
+
* y-up page frame) between `x0` and `x1`.
|
|
1666
|
+
*
|
|
1667
|
+
* @returns The fill and border items, in paint order.
|
|
1668
|
+
*/
|
|
1669
|
+
function paragraphDecoration(resolved, x0, x1, topY, bottomY, pageHeight) {
|
|
1670
|
+
const out = [];
|
|
1671
|
+
const width = Math.max(0, x1 - x0);
|
|
1672
|
+
if (resolved.shading) out.push({
|
|
1673
|
+
type: "fill",
|
|
1674
|
+
x: pt(x0),
|
|
1675
|
+
y: pt(pageHeight - topY),
|
|
1676
|
+
width: pt(width),
|
|
1677
|
+
height: pt(Math.max(0, topY - bottomY)),
|
|
1678
|
+
fillColorHex: resolved.shading.colorHex
|
|
1679
|
+
});
|
|
1680
|
+
const borders = resolved.borders;
|
|
1681
|
+
if (!borders) return out;
|
|
1682
|
+
for (const side of [
|
|
1683
|
+
"top",
|
|
1684
|
+
"bottom",
|
|
1685
|
+
"left",
|
|
1686
|
+
"right"
|
|
1687
|
+
]) {
|
|
1688
|
+
const edge = borders[side];
|
|
1689
|
+
if (!edge || edge.style === "none") continue;
|
|
1690
|
+
const gap = edge.spacePt ?? 0;
|
|
1691
|
+
const top = topY + gap;
|
|
1692
|
+
const bottom = bottomY - gap;
|
|
1693
|
+
out.push({
|
|
1694
|
+
type: "border",
|
|
1695
|
+
side,
|
|
1696
|
+
x: pt(x0),
|
|
1697
|
+
y: pt(pageHeight - top),
|
|
1698
|
+
width: pt(width),
|
|
1699
|
+
height: pt(Math.max(0, top - bottom)),
|
|
1700
|
+
borderSizePt: edge.width ?? DEFAULT_BORDER_SIZE_EIGHTH * EIGHTH_PT,
|
|
1701
|
+
borderColorHex: edge.colorHex ?? "000000",
|
|
1702
|
+
...edge.style !== "single" ? { borderStyle: edge.style } : {}
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
return out;
|
|
1706
|
+
}
|
|
968
1707
|
function runFontKeyAndParsed(options, ascii, bold, italic) {
|
|
969
1708
|
if (ascii && options.embeddedFonts) {
|
|
970
1709
|
const name = ascii.trim().toLowerCase();
|
|
@@ -1015,6 +1754,10 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1015
1754
|
}
|
|
1016
1755
|
const shaped = shapeText(run.text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, parsed.kerning, parsed.joiningForms);
|
|
1017
1756
|
for (const g of shaped.gids) bucket.gids.add(g);
|
|
1757
|
+
if (run.text.includes(" ")) for (const stop of resolveParagraphProperties(para.properties, options.styles).tabs) {
|
|
1758
|
+
const ch = stop.leader === void 0 ? void 0 : TAB_LEADER_CHARS.get(stop.leader);
|
|
1759
|
+
if (ch !== void 0) bucket.gids.add(parsed.glyphForCodepoint(ch.codePointAt(0)));
|
|
1760
|
+
}
|
|
1018
1761
|
};
|
|
1019
1762
|
const addMath = (node) => {
|
|
1020
1763
|
for (const seg of mathGlyphSegments(node)) {
|
|
@@ -1032,7 +1775,7 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1032
1775
|
}
|
|
1033
1776
|
};
|
|
1034
1777
|
const visit = (elements) => {
|
|
1035
|
-
for (const el of elements) if (el.kind === "paragraph") for (const run of el.paragraph
|
|
1778
|
+
for (const el of elements) if (el.kind === "paragraph") for (const run of displayCapsRuns(el.paragraph, options)) {
|
|
1036
1779
|
if (run.math) {
|
|
1037
1780
|
addMath(run.math);
|
|
1038
1781
|
continue;
|
|
@@ -1065,7 +1808,11 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1065
1808
|
visit(cell.content);
|
|
1066
1809
|
}
|
|
1067
1810
|
else if (el.kind === "shape") {
|
|
1068
|
-
|
|
1811
|
+
const shapeText = (sh) => {
|
|
1812
|
+
if (sh.text) visit(sh.text.content);
|
|
1813
|
+
for (const child of sh.children ?? []) shapeText(child.shape);
|
|
1814
|
+
};
|
|
1815
|
+
shapeText(el.shape);
|
|
1069
1816
|
} else if (el.kind === "chart") {
|
|
1070
1817
|
const chart = options.charts?.get(el.chart.chartRelId);
|
|
1071
1818
|
if (chart) {
|
|
@@ -1093,6 +1840,16 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1093
1840
|
}
|
|
1094
1841
|
};
|
|
1095
1842
|
visit(body);
|
|
1843
|
+
if ((options.sections ?? []).some((sec) => sec.properties.lineNumbering)) visit([{
|
|
1844
|
+
kind: "paragraph",
|
|
1845
|
+
paragraph: {
|
|
1846
|
+
properties: {},
|
|
1847
|
+
runs: [{
|
|
1848
|
+
text: "0123456789",
|
|
1849
|
+
properties: {}
|
|
1850
|
+
}]
|
|
1851
|
+
}
|
|
1852
|
+
}]);
|
|
1096
1853
|
for (const hf of headersFooters.values()) visit(hf);
|
|
1097
1854
|
for (const note of options.footnotes?.values() ?? []) visit(note);
|
|
1098
1855
|
for (const note of options.endnotes?.values() ?? []) visit(note);
|
|
@@ -1135,17 +1892,83 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1135
1892
|
}
|
|
1136
1893
|
return out;
|
|
1137
1894
|
}
|
|
1895
|
+
/**
|
|
1896
|
+
* The one line an empty paragraph still occupies — no tokens, but as tall as
|
|
1897
|
+
* the paragraph mark's own font (§17.3.1.31 `w:rPr` on the `w:pPr`, which is
|
|
1898
|
+
* exactly the run properties Word keeps for a paragraph that holds no runs).
|
|
1899
|
+
*/
|
|
1900
|
+
function emptyLine(paragraph, options, resolved, availableWidthPt, fontResources) {
|
|
1901
|
+
const mark = resolveRunProperties(paragraph.properties.runProperties ?? {}, paragraph.properties, options.styles);
|
|
1902
|
+
const fontSizePt = mark.fontSizePt;
|
|
1903
|
+
const profile = options.layoutProfile ?? "ream";
|
|
1904
|
+
const { variant } = options.registry.resolveByStyle(mark.bold, mark.italic);
|
|
1905
|
+
const parsed = fontResources.get(variant)?.parsed;
|
|
1906
|
+
const metric = profile !== "ream" && parsed ? fontLeadingPt(parsed, fontSizePt, profile) : void 0;
|
|
1907
|
+
return {
|
|
1908
|
+
tokens: [],
|
|
1909
|
+
contentWidthPt: 0,
|
|
1910
|
+
maxFontSizePt: fontSizePt,
|
|
1911
|
+
availableWidthPt,
|
|
1912
|
+
firstLine: true,
|
|
1913
|
+
resolved,
|
|
1914
|
+
isLastInParagraph: true,
|
|
1915
|
+
...metric && metric.ascentPt + metric.descentPt > 0 ? {
|
|
1916
|
+
metricHeightPt: metric.ascentPt + metric.descentPt + metric.lineGapPt,
|
|
1917
|
+
metricDescentPt: metric.descentPt
|
|
1918
|
+
} : {}
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1138
1921
|
function layoutParagraphBlock(paragraph, options, fontResources, imageResources, contentWidth, lineWidths) {
|
|
1139
|
-
const
|
|
1922
|
+
const cascaded = resolveParagraphProperties(paragraph.properties, options.styles);
|
|
1923
|
+
const baseResolved = options.gridLinePitchPt !== void 0 && cascaded.snapToGrid !== false ? {
|
|
1924
|
+
...cascaded,
|
|
1925
|
+
gridLinePitchPt: options.gridLinePitchPt
|
|
1926
|
+
} : cascaded;
|
|
1927
|
+
if (paragraph.properties.sectionBreak === true && paragraph.runs.length === 0) return {
|
|
1928
|
+
kind: "paragraph",
|
|
1929
|
+
resolved: baseResolved,
|
|
1930
|
+
lines: [],
|
|
1931
|
+
heightPt: 0,
|
|
1932
|
+
spacingBeforePt: 0,
|
|
1933
|
+
spacingAfterPt: 0
|
|
1934
|
+
};
|
|
1140
1935
|
const baseDir = paragraphBaseDirection(paragraph, baseResolved);
|
|
1141
|
-
const
|
|
1936
|
+
const swapAlign = (a) => a === "left" ? "right" : a === "right" ? "left" : a;
|
|
1937
|
+
const resolved = baseResolved.bidi === true ? {
|
|
1938
|
+
...baseResolved,
|
|
1939
|
+
alignment: swapAlign(baseResolved.alignment),
|
|
1940
|
+
indentLeft: baseResolved.indentRight,
|
|
1941
|
+
indentRight: baseResolved.indentLeft
|
|
1942
|
+
} : baseDir === "rtl" && baseResolved.alignment === "left" ? {
|
|
1142
1943
|
...baseResolved,
|
|
1143
1944
|
alignment: "right"
|
|
1144
1945
|
} : baseResolved;
|
|
1145
|
-
const
|
|
1946
|
+
const tokens = tokenizeParagraph(paragraph, options, fontResources, imageResources, contentWidth, baseDir);
|
|
1947
|
+
const firstLineWidth = paragraphMaxWidth(resolved, contentWidth, true);
|
|
1948
|
+
const otherWidth = paragraphMaxWidth(resolved, contentWidth, false);
|
|
1949
|
+
const runWrap = (part, firstWidth, widths) => wrap(part, firstWidth, otherWidth, resolved, options.hyphenator, options.layoutProfile ?? "ream", options.doNotExpandShiftReturn === true, widths);
|
|
1950
|
+
const parts = splitAtColumnBreaks(tokens);
|
|
1951
|
+
const columnBreakLines = /* @__PURE__ */ new Set();
|
|
1952
|
+
let wrapped;
|
|
1953
|
+
if (parts.length === 1) wrapped = runWrap(tokens, firstLineWidth, lineWidths);
|
|
1954
|
+
else {
|
|
1955
|
+
wrapped = [];
|
|
1956
|
+
for (const [i, part] of parts.entries()) {
|
|
1957
|
+
const lines = runWrap(part, i === 0 ? firstLineWidth : otherWidth, lineWidths?.slice(wrapped.length));
|
|
1958
|
+
if (i > 0 && lines.length > 0) columnBreakLines.add(wrapped.length);
|
|
1959
|
+
wrapped.push(...i === 0 ? lines : lines.map((l) => ({
|
|
1960
|
+
...l,
|
|
1961
|
+
firstLine: false
|
|
1962
|
+
})));
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
const lines = wrapped.length > 0 ? wrapped : [emptyLine(paragraph, options, resolved, Math.max(firstLineWidth, 0), fontResources)];
|
|
1146
1966
|
let heightPt = 0;
|
|
1147
1967
|
for (const line of lines) heightPt += computeLineHeight(line, resolved);
|
|
1148
1968
|
const numbering = paragraph.properties.numbering;
|
|
1969
|
+
const breakIdx = paragraph.runs.findIndex((r) => r.pageBreak === true);
|
|
1970
|
+
const leadingPageBreak = breakIdx >= 0 && paragraph.runs.slice(0, breakIdx + 1).every((r) => r.text === "" && r.inlineImage === void 0);
|
|
1971
|
+
const trailingPageBreak = paragraph.runs.some((r, i) => r.pageBreak === true && (!leadingPageBreak || i > breakIdx));
|
|
1149
1972
|
return {
|
|
1150
1973
|
kind: "paragraph",
|
|
1151
1974
|
resolved,
|
|
@@ -1157,7 +1980,9 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
|
|
|
1157
1980
|
numId: numbering.numId,
|
|
1158
1981
|
level: numbering.ilvl
|
|
1159
1982
|
} } : {},
|
|
1160
|
-
...
|
|
1983
|
+
...leadingPageBreak ? { pageBreakBefore: true } : {},
|
|
1984
|
+
...trailingPageBreak ? { pageBreakAfter: true } : {},
|
|
1985
|
+
...columnBreakLines.size > 0 ? { columnBreakLines } : {},
|
|
1161
1986
|
...paragraph.bookmarks && paragraph.bookmarks.length > 0 ? { bookmarks: paragraph.bookmarks } : {},
|
|
1162
1987
|
source: paragraph
|
|
1163
1988
|
};
|
|
@@ -1197,13 +2022,76 @@ function resolveMathItems(items, fontFor) {
|
|
|
1197
2022
|
};
|
|
1198
2023
|
});
|
|
1199
2024
|
}
|
|
2025
|
+
/**
|
|
2026
|
+
* §17.3.2.5 `w:caps` / §17.3.2.33 `w:smallCaps` — the paragraph's runs as they
|
|
2027
|
+
* are DISPLAYED. A capitals run is upper-cased; a small-capitals run is too,
|
|
2028
|
+
* and the letters that were lower case are split out to be set smaller.
|
|
2029
|
+
*
|
|
2030
|
+
* @param paragraph The paragraph.
|
|
2031
|
+
* @param options The render options (for the style cascade).
|
|
2032
|
+
* @returns The runs to lay out — the same array when none asks for capitals.
|
|
2033
|
+
*/
|
|
2034
|
+
function displayCapsRuns(paragraph, options) {
|
|
2035
|
+
let any = false;
|
|
2036
|
+
const out = [];
|
|
2037
|
+
for (const raw of paragraph.runs) {
|
|
2038
|
+
const run = raw.text.includes(SOFT_HYPHEN) ? (any = true, {
|
|
2039
|
+
...raw,
|
|
2040
|
+
text: raw.text.replaceAll(SOFT_HYPHEN, "")
|
|
2041
|
+
}) : raw;
|
|
2042
|
+
const resolved = run.text.length > 0 ? resolveRunProperties(run.properties, paragraph.properties, options.styles) : void 0;
|
|
2043
|
+
if (!resolved || !resolved.caps && !resolved.smallCaps) {
|
|
2044
|
+
out.push(run);
|
|
2045
|
+
continue;
|
|
2046
|
+
}
|
|
2047
|
+
any = true;
|
|
2048
|
+
if (resolved.caps) {
|
|
2049
|
+
out.push({
|
|
2050
|
+
...run,
|
|
2051
|
+
text: run.text.toUpperCase()
|
|
2052
|
+
});
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
let start = 0;
|
|
2056
|
+
const wasLower = (i) => {
|
|
2057
|
+
const ch = run.text[i];
|
|
2058
|
+
return ch.toLowerCase() === ch && ch.toUpperCase() !== ch;
|
|
2059
|
+
};
|
|
2060
|
+
for (let i = 1; i <= run.text.length; i++) {
|
|
2061
|
+
if (i < run.text.length && wasLower(i) === wasLower(start)) continue;
|
|
2062
|
+
const segment = run.text.slice(start, i).toUpperCase();
|
|
2063
|
+
out.push(wasLower(start) ? {
|
|
2064
|
+
...run,
|
|
2065
|
+
text: segment,
|
|
2066
|
+
properties: {
|
|
2067
|
+
...run.properties,
|
|
2068
|
+
fontSizePt: pt(resolved.fontSizePt * .8)
|
|
2069
|
+
}
|
|
2070
|
+
} : {
|
|
2071
|
+
...run,
|
|
2072
|
+
text: segment
|
|
2073
|
+
});
|
|
2074
|
+
start = i;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
return any ? out : paragraph.runs;
|
|
2078
|
+
}
|
|
1200
2079
|
function tokenizeParagraph(paragraph, options, fontResources, imageResources, contentWidth, baseDir) {
|
|
1201
|
-
const plans = paragraph.
|
|
2080
|
+
const plans = displayCapsRuns(paragraph, options).map((run) => {
|
|
1202
2081
|
if (run.inlineImage) {
|
|
1203
|
-
const
|
|
2082
|
+
const eff = run.inlineImage.effectExtent;
|
|
2083
|
+
const padW = (eff?.leftPt ?? 0) + (eff?.rightPt ?? 0);
|
|
2084
|
+
const padH = (eff?.topPt ?? 0) + (eff?.bottomPt ?? 0);
|
|
2085
|
+
const naturalW = run.inlineImage.width + padW;
|
|
1204
2086
|
const widthPt = Math.min(naturalW, contentWidth);
|
|
1205
2087
|
const scale = naturalW > 0 ? widthPt / naturalW : 1;
|
|
1206
|
-
const heightPt = run.inlineImage.height * scale;
|
|
2088
|
+
const heightPt = (run.inlineImage.height + padH) * scale;
|
|
2089
|
+
const drawBox = eff ? {
|
|
2090
|
+
dxPt: eff.leftPt * scale,
|
|
2091
|
+
dyPt: eff.bottomPt * scale,
|
|
2092
|
+
widthPt: run.inlineImage.width * scale,
|
|
2093
|
+
heightPt: run.inlineImage.height * scale
|
|
2094
|
+
} : void 0;
|
|
1207
2095
|
const res = run.inlineImage.resource ? imageResources?.get(run.inlineImage.resource) : void 0;
|
|
1208
2096
|
const resolvedRun = resolveRunProperties(run.properties, paragraph.properties, options.styles);
|
|
1209
2097
|
const { fontKey } = runFontKeyAndParsed(options, resolvedRun.fontFamily.ascii, resolvedRun.bold, resolvedRun.italic);
|
|
@@ -1215,7 +2103,14 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
|
|
|
1215
2103
|
isImage: true,
|
|
1216
2104
|
imageWidthPt: widthPt,
|
|
1217
2105
|
imageHeightPt: heightPt,
|
|
1218
|
-
imageResourceName: res?.resourceName ?? ""
|
|
2106
|
+
imageResourceName: res?.resourceName ?? "",
|
|
2107
|
+
...run.inlineImage.outline ? { imageOutline: run.inlineImage.outline } : {},
|
|
2108
|
+
...run.inlineImage.shadow ? { imageShadow: run.inlineImage.shadow } : {},
|
|
2109
|
+
...run.inlineImage.crop ? { imageCrop: run.inlineImage.crop } : {},
|
|
2110
|
+
...run.inlineImage.rotation60k ? { imageRotationDeg: run.inlineImage.rotation60k / 6e4 } : {},
|
|
2111
|
+
...run.inlineImage.flipH ? { imageFlipH: true } : {},
|
|
2112
|
+
...run.inlineImage.flipV ? { imageFlipV: true } : {},
|
|
2113
|
+
...drawBox ? { imageDrawBox: drawBox } : {}
|
|
1219
2114
|
};
|
|
1220
2115
|
}
|
|
1221
2116
|
if (run.math) {
|
|
@@ -1270,6 +2165,19 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
|
|
|
1270
2165
|
...plan.resolvedRun.rtl ? { rtl: true } : {}
|
|
1271
2166
|
})), baseDir));
|
|
1272
2167
|
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Split a paragraph's tokens at every §17.3.3.1 column break, dropping the
|
|
2170
|
+
* break token itself (its line ends there anyway). One part ⇒ no break.
|
|
2171
|
+
*
|
|
2172
|
+
* @param tokens The paragraph's tokens in order.
|
|
2173
|
+
* @returns The parts, in order; always at least one.
|
|
2174
|
+
*/
|
|
2175
|
+
function splitAtColumnBreaks(tokens) {
|
|
2176
|
+
const parts = [[]];
|
|
2177
|
+
for (const t of tokens) if (t.kind === "text" && t.columnBreak) parts.push([]);
|
|
2178
|
+
else parts[parts.length - 1].push(t);
|
|
2179
|
+
return parts.filter((p, i) => i === 0 || p.length > 0);
|
|
2180
|
+
}
|
|
1273
2181
|
function tokenizePlansLtr(plans) {
|
|
1274
2182
|
const tokens = [];
|
|
1275
2183
|
for (const plan of plans) {
|
|
@@ -1279,6 +2187,13 @@ function tokenizePlansLtr(plans) {
|
|
|
1279
2187
|
imageResourceName: plan.imageResourceName,
|
|
1280
2188
|
widthPt: plan.imageWidthPt,
|
|
1281
2189
|
heightPt: plan.imageHeightPt,
|
|
2190
|
+
...plan.imageOutline ? { outline: plan.imageOutline } : {},
|
|
2191
|
+
...plan.imageShadow ? { shadow: plan.imageShadow } : {},
|
|
2192
|
+
...plan.imageCrop ? { crop: plan.imageCrop } : {},
|
|
2193
|
+
...plan.imageRotationDeg ? { rotationDeg: plan.imageRotationDeg } : {},
|
|
2194
|
+
...plan.imageFlipH ? { flipH: true } : {},
|
|
2195
|
+
...plan.imageFlipV ? { flipV: true } : {},
|
|
2196
|
+
...plan.imageDrawBox ? { drawBox: plan.imageDrawBox } : {},
|
|
1282
2197
|
isSpace: false,
|
|
1283
2198
|
bidiLevel: 0
|
|
1284
2199
|
});
|
|
@@ -1301,21 +2216,37 @@ function tokenizePlansLtr(plans) {
|
|
|
1301
2216
|
kind: "text",
|
|
1302
2217
|
text: t.text,
|
|
1303
2218
|
isSpace: t.isSpace,
|
|
2219
|
+
...t.tab ? { tab: true } : {},
|
|
1304
2220
|
...plan.run.href !== void 0 ? { href: plan.run.href } : {},
|
|
1305
2221
|
...plan.run.footnoteRef !== void 0 ? { footnoteRef: plan.run.footnoteRef } : {},
|
|
1306
2222
|
...plan.run.anchor !== void 0 ? { anchor: plan.run.anchor } : {},
|
|
1307
2223
|
...plan.run.listMarker ? { listMarker: true } : {},
|
|
2224
|
+
...plan.run.columnBreak ? { columnBreak: true } : {},
|
|
1308
2225
|
...highlight ? { highlight: true } : {},
|
|
1309
2226
|
resolvedRun: plan.resolvedRun,
|
|
1310
2227
|
font: plan.font,
|
|
1311
2228
|
fontSizePt: plan.fontSizePt,
|
|
1312
2229
|
...plan.risePt !== void 0 ? { risePt: plan.risePt } : {},
|
|
1313
|
-
widthPt: plan
|
|
2230
|
+
widthPt: measureRunText(plan, t.text),
|
|
1314
2231
|
bidiLevel: 0
|
|
1315
2232
|
});
|
|
1316
2233
|
}
|
|
1317
2234
|
return tokens;
|
|
1318
2235
|
}
|
|
2236
|
+
/**
|
|
2237
|
+
* §17.3.2.35 — a run's text measured with the space it puts between its
|
|
2238
|
+
* characters. PDF adds the same amount per glyph through `Tc`, so the width the
|
|
2239
|
+
* line is broken at and the width the page draws agree.
|
|
2240
|
+
*
|
|
2241
|
+
* @param plan The run being tokenized.
|
|
2242
|
+
* @param text The token's text.
|
|
2243
|
+
* @returns The advance width in points.
|
|
2244
|
+
*/
|
|
2245
|
+
function measureRunText(plan, text) {
|
|
2246
|
+
const base = plan.font.measure.textWidthPt(text, plan.fontSizePt);
|
|
2247
|
+
const extra = plan.resolvedRun.letterSpacingPt;
|
|
2248
|
+
return extra === void 0 || extra === 0 ? base : base + extra * [...text].length;
|
|
2249
|
+
}
|
|
1319
2250
|
function tokenizePlansBidi(plans, realLevels) {
|
|
1320
2251
|
const tokens = [];
|
|
1321
2252
|
let realIdx = 0;
|
|
@@ -1327,6 +2258,13 @@ function tokenizePlansBidi(plans, realLevels) {
|
|
|
1327
2258
|
imageResourceName: plan.imageResourceName,
|
|
1328
2259
|
widthPt: plan.imageWidthPt,
|
|
1329
2260
|
heightPt: plan.imageHeightPt,
|
|
2261
|
+
...plan.imageOutline ? { outline: plan.imageOutline } : {},
|
|
2262
|
+
...plan.imageShadow ? { shadow: plan.imageShadow } : {},
|
|
2263
|
+
...plan.imageCrop ? { crop: plan.imageCrop } : {},
|
|
2264
|
+
...plan.imageRotationDeg ? { rotationDeg: plan.imageRotationDeg } : {},
|
|
2265
|
+
...plan.imageFlipH ? { flipH: true } : {},
|
|
2266
|
+
...plan.imageFlipV ? { flipV: true } : {},
|
|
2267
|
+
...plan.imageDrawBox ? { drawBox: plan.imageDrawBox } : {},
|
|
1330
2268
|
isSpace: false,
|
|
1331
2269
|
bidiLevel: realLevels[realIdx] ?? 0
|
|
1332
2270
|
});
|
|
@@ -1361,12 +2299,13 @@ function tokenizePlansBidi(plans, realLevels) {
|
|
|
1361
2299
|
...plan.run.footnoteRef !== void 0 ? { footnoteRef: plan.run.footnoteRef } : {},
|
|
1362
2300
|
...plan.run.anchor !== void 0 ? { anchor: plan.run.anchor } : {},
|
|
1363
2301
|
...plan.run.listMarker ? { listMarker: true } : {},
|
|
2302
|
+
...plan.run.columnBreak ? { columnBreak: true } : {},
|
|
1364
2303
|
...(plan.run.commentRangeRefs?.length ?? 0) > 0 ? { highlight: true } : {},
|
|
1365
2304
|
resolvedRun: plan.resolvedRun,
|
|
1366
2305
|
font: plan.font,
|
|
1367
2306
|
fontSizePt: plan.fontSizePt,
|
|
1368
2307
|
...plan.risePt !== void 0 ? { risePt: plan.risePt } : {},
|
|
1369
|
-
widthPt: plan
|
|
2308
|
+
widthPt: measureRunText(plan, text),
|
|
1370
2309
|
bidiLevel: curLevel
|
|
1371
2310
|
});
|
|
1372
2311
|
};
|
|
@@ -1393,6 +2332,7 @@ function paragraphBaseDirection(paragraph, resolved) {
|
|
|
1393
2332
|
if (resolved.bidi) return "rtl";
|
|
1394
2333
|
let text = "";
|
|
1395
2334
|
for (const run of paragraph.runs) {
|
|
2335
|
+
if (run.listMarker === true) continue;
|
|
1396
2336
|
if (!run.inlineImage) text += run.text;
|
|
1397
2337
|
if (text.length > 64) break;
|
|
1398
2338
|
}
|
|
@@ -1405,16 +2345,29 @@ function paragraphMaxWidth(p, contentWidth, firstLine) {
|
|
|
1405
2345
|
const firstLineExtra = firstLine ? p.indentFirstLine : 0;
|
|
1406
2346
|
return Math.max(1, contentWidth - indentLeft - indentRight - firstLineExtra);
|
|
1407
2347
|
}
|
|
2348
|
+
var HYPHENS = new Set([
|
|
2349
|
+
45,
|
|
2350
|
+
8208,
|
|
2351
|
+
8211,
|
|
2352
|
+
8212
|
|
2353
|
+
]);
|
|
2354
|
+
/** §17.3.3.29 — the optional hyphen, printed only where a line breaks on it. */
|
|
2355
|
+
var SOFT_HYPHEN = "";
|
|
1408
2356
|
function tokenizeText(text) {
|
|
1409
2357
|
const out = [];
|
|
1410
2358
|
if (text.length === 0) return out;
|
|
1411
|
-
const re = /(\s+)|(\S+)/g;
|
|
2359
|
+
const re = /(\t)|(\s+)|(\S+)/g;
|
|
1412
2360
|
let m;
|
|
1413
2361
|
while ((m = re.exec(text)) !== null) if (m[1] !== void 0) out.push({
|
|
1414
|
-
text:
|
|
2362
|
+
text: "",
|
|
2363
|
+
isSpace: true,
|
|
2364
|
+
tab: true
|
|
2365
|
+
});
|
|
2366
|
+
else if (m[2] !== void 0) out.push({
|
|
2367
|
+
text: m[2],
|
|
1415
2368
|
isSpace: true
|
|
1416
2369
|
});
|
|
1417
|
-
else if (m[
|
|
2370
|
+
else if (m[3] !== void 0) for (const piece of splitCjkSegment(m[3])) out.push({
|
|
1418
2371
|
text: piece,
|
|
1419
2372
|
isSpace: false
|
|
1420
2373
|
});
|
|
@@ -1428,14 +2381,51 @@ function lastCodePoint(s) {
|
|
|
1428
2381
|
function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
1429
2382
|
const entries = [];
|
|
1430
2383
|
let prevBoxEndCp = null;
|
|
2384
|
+
let prevWasImage = false;
|
|
1431
2385
|
for (const tok of tokens) {
|
|
2386
|
+
const breaks = tok.isSpace ? tok.text.match(/\r\n|[\r\n]/gu)?.length ?? 0 : 0;
|
|
2387
|
+
if (breaks > 0) {
|
|
2388
|
+
for (let i = 0; i < breaks; i++) {
|
|
2389
|
+
entries.push({
|
|
2390
|
+
item: {
|
|
2391
|
+
type: "glue",
|
|
2392
|
+
width: 0,
|
|
2393
|
+
stretch: 1e6,
|
|
2394
|
+
shrink: 0
|
|
2395
|
+
},
|
|
2396
|
+
token: null
|
|
2397
|
+
});
|
|
2398
|
+
entries.push({
|
|
2399
|
+
item: {
|
|
2400
|
+
type: "penalty",
|
|
2401
|
+
width: 0,
|
|
2402
|
+
penalty: FORCED_BREAK,
|
|
2403
|
+
flagged: false
|
|
2404
|
+
},
|
|
2405
|
+
token: null,
|
|
2406
|
+
softBreak: true
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
prevBoxEndCp = null;
|
|
2410
|
+
continue;
|
|
2411
|
+
}
|
|
1432
2412
|
if (tok.isSpace || tok.kind === "image" || tok.kind === "math") {
|
|
2413
|
+
if (tok.kind === "image" && (prevWasImage || prevBoxEndCp !== null)) entries.push({
|
|
2414
|
+
item: {
|
|
2415
|
+
type: "penalty",
|
|
2416
|
+
width: 0,
|
|
2417
|
+
penalty: 0,
|
|
2418
|
+
flagged: false
|
|
2419
|
+
},
|
|
2420
|
+
token: null
|
|
2421
|
+
});
|
|
2422
|
+
prevWasImage = tok.kind === "image";
|
|
1433
2423
|
entries.push({
|
|
1434
2424
|
item: tok.isSpace ? {
|
|
1435
2425
|
type: "glue",
|
|
1436
2426
|
width: tok.widthPt,
|
|
1437
2427
|
stretch: tok.widthPt * .6,
|
|
1438
|
-
shrink: tok.widthPt *
|
|
2428
|
+
shrink: tok.widthPt * GLUE_SHRINK_RATIO
|
|
1439
2429
|
} : {
|
|
1440
2430
|
type: "box",
|
|
1441
2431
|
width: tok.widthPt
|
|
@@ -1446,7 +2436,7 @@ function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
|
1446
2436
|
continue;
|
|
1447
2437
|
}
|
|
1448
2438
|
const startCp = tok.text.codePointAt(0) ?? 0;
|
|
1449
|
-
if (prevBoxEndCp !== null && cjkBreakBetween(prevBoxEndCp, startCp)) entries.push({
|
|
2439
|
+
if (prevWasImage || prevBoxEndCp !== null && cjkBreakBetween(prevBoxEndCp, startCp)) entries.push({
|
|
1450
2440
|
item: {
|
|
1451
2441
|
type: "penalty",
|
|
1452
2442
|
width: 0,
|
|
@@ -1455,10 +2445,12 @@ function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
|
1455
2445
|
},
|
|
1456
2446
|
token: null
|
|
1457
2447
|
});
|
|
2448
|
+
prevWasImage = false;
|
|
1458
2449
|
prevBoxEndCp = lastCodePoint(tok.text);
|
|
1459
2450
|
const positions = hyphenator ? hyphenator.hyphenate(tok.text) : [];
|
|
1460
2451
|
if (positions.length === 0) {
|
|
1461
|
-
const
|
|
2452
|
+
const tooWide = maxLineWidthPt !== void 0 && maxLineWidthPt > 0;
|
|
2453
|
+
const chunks = (tooWide && tok.widthPt > maxLineWidthPt ? splitAtHyphens(tok) : void 0) ?? (tooWide && tok.widthPt > maxLineWidthPt * 1.1 ? splitToWidth(tok, maxLineWidthPt) : void 0);
|
|
1462
2454
|
if (chunks) {
|
|
1463
2455
|
chunks.forEach((chunk, ci) => {
|
|
1464
2456
|
if (ci > 0) entries.push({
|
|
@@ -1545,6 +2537,94 @@ function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
|
1545
2537
|
});
|
|
1546
2538
|
return entries;
|
|
1547
2539
|
}
|
|
2540
|
+
/** Word's own default when a document names none (§17.15.1.25): half an inch. */
|
|
2541
|
+
var DEFAULT_TAB_STOP_PT = 36;
|
|
2542
|
+
var TAB_LEADER_CHARS = new Map([
|
|
2543
|
+
["dot", "."],
|
|
2544
|
+
["hyphen", "-"],
|
|
2545
|
+
["underscore", "_"],
|
|
2546
|
+
["middleDot", "·"]
|
|
2547
|
+
]);
|
|
2548
|
+
/**
|
|
2549
|
+
* Give each tab on a line the width that carries it to its stop, and the
|
|
2550
|
+
* leader that fills the way there.
|
|
2551
|
+
*
|
|
2552
|
+
* §17.3.1.37 — a tab advances to a POSITION, not by a fixed amount, and where
|
|
2553
|
+
* that position is depends on how far the line has already run. Measured as
|
|
2554
|
+
* ordinary whitespace it collapsed to a space: the page numbers of
|
|
2555
|
+
* FDO77715.docx's index sat against their titles with no dot leader between,
|
|
2556
|
+
* and its header ran its left and right halves together on one line.
|
|
2557
|
+
*
|
|
2558
|
+
* A `right`, `center` or `decimal` stop positions the text AFTER the tab, so
|
|
2559
|
+
* each is resolved against the segment that follows it.
|
|
2560
|
+
*
|
|
2561
|
+
* @param tokens The line's tokens, replaced in place.
|
|
2562
|
+
* @param resolved The paragraph's resolved properties: the stops and indents.
|
|
2563
|
+
* @param isFirst Whether this is the paragraph's first line.
|
|
2564
|
+
*/
|
|
2565
|
+
function resolveTabs(tokens, resolved, isFirst, availableWidthPt) {
|
|
2566
|
+
const stops = resolved.tabs;
|
|
2567
|
+
let x = resolved.indentLeft + (isFirst ? resolved.indentFirstLine : 0);
|
|
2568
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2569
|
+
const tok = tokens[i];
|
|
2570
|
+
if (!tok) continue;
|
|
2571
|
+
if (!(tok.kind === "text" && tok.tab === true)) {
|
|
2572
|
+
x += tok.widthPt;
|
|
2573
|
+
continue;
|
|
2574
|
+
}
|
|
2575
|
+
let segment = 0;
|
|
2576
|
+
let decimalAt = 0;
|
|
2577
|
+
let seenDecimal = false;
|
|
2578
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
2579
|
+
const next = tokens[j];
|
|
2580
|
+
if (next.kind === "text" && next.tab === true) break;
|
|
2581
|
+
if (!seenDecimal && next.kind === "text") {
|
|
2582
|
+
const dot = next.text.indexOf(".");
|
|
2583
|
+
if (dot >= 0) {
|
|
2584
|
+
decimalAt = segment + next.font.measure.textWidthPt(next.text.slice(0, dot), next.fontSizePt);
|
|
2585
|
+
seenDecimal = true;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
segment += next.widthPt;
|
|
2589
|
+
}
|
|
2590
|
+
const stopAt = (sp) => sp.relativeTo === "center" ? availableWidthPt / 2 : sp.relativeTo === "right" ? availableWidthPt : sp.positionPt;
|
|
2591
|
+
const stop = stops.find((sp) => stopAt(sp) > x + .01);
|
|
2592
|
+
const hangingStop = isFirst && resolved.indentFirstLine < 0 && resolved.indentLeft > x + .01 ? resolved.indentLeft : void 0;
|
|
2593
|
+
const explicitAt = stop ? stopAt(stop) : void 0;
|
|
2594
|
+
const position = explicitAt !== void 0 && (hangingStop === void 0 || explicitAt <= hangingStop) ? explicitAt : hangingStop ?? (Math.floor(x / DEFAULT_TAB_STOP_PT) + 1) * DEFAULT_TAB_STOP_PT;
|
|
2595
|
+
const alignment = stop?.alignment ?? "left";
|
|
2596
|
+
if (segment === 0 && alignment !== "left") {
|
|
2597
|
+
const after = tokens[i + 1];
|
|
2598
|
+
if (after?.kind === "text" && after.tab === true) {
|
|
2599
|
+
tokens[i] = {
|
|
2600
|
+
...tok,
|
|
2601
|
+
widthPt: 0,
|
|
2602
|
+
text: ""
|
|
2603
|
+
};
|
|
2604
|
+
continue;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
let target = position;
|
|
2608
|
+
if (alignment === "right") target = position - segment;
|
|
2609
|
+
else if (alignment === "center") target = position - segment / 2;
|
|
2610
|
+
else if (alignment === "decimal") target = position - (seenDecimal ? decimalAt : segment);
|
|
2611
|
+
const width = Math.max(0, target - x);
|
|
2612
|
+
const leaderChar = stop?.leader === void 0 ? void 0 : TAB_LEADER_CHARS.get(stop.leader);
|
|
2613
|
+
const text = leaderChar === void 0 ? "" : fillLeader(tok, leaderChar, width);
|
|
2614
|
+
tokens[i] = {
|
|
2615
|
+
...tok,
|
|
2616
|
+
widthPt: width,
|
|
2617
|
+
text
|
|
2618
|
+
};
|
|
2619
|
+
x += width;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
/** As many leader characters as fit the gap, measured in the tab's own font. */
|
|
2623
|
+
function fillLeader(tab, leader, widthPt) {
|
|
2624
|
+
const unit = tab.font.measure.textWidthPt(leader, tab.fontSizePt);
|
|
2625
|
+
if (!(unit > 0)) return "";
|
|
2626
|
+
return leader.repeat(Math.max(0, Math.floor(widthPt / unit)));
|
|
2627
|
+
}
|
|
1548
2628
|
/**
|
|
1549
2629
|
* Cut an unbreakable word into the widest pieces that fit `widthPt`, measured
|
|
1550
2630
|
* with the token's own font. Each piece keeps the run's formatting, so the line
|
|
@@ -1555,6 +2635,24 @@ function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
|
1555
2635
|
* @returns The pieces in order, or undefined when even one character overruns
|
|
1556
2636
|
* (nothing can be gained by splitting then).
|
|
1557
2637
|
*/
|
|
2638
|
+
function splitAtHyphens(tok) {
|
|
2639
|
+
if (tok.kind !== "text") return void 0;
|
|
2640
|
+
const parts = [];
|
|
2641
|
+
let start = 0;
|
|
2642
|
+
for (let i = 0; i < tok.text.length; i++) {
|
|
2643
|
+
if (!HYPHENS.has(tok.text.codePointAt(i) ?? 0)) continue;
|
|
2644
|
+
if (i === start || i === tok.text.length - 1) continue;
|
|
2645
|
+
parts.push(tok.text.slice(start, i + 1));
|
|
2646
|
+
start = i + 1;
|
|
2647
|
+
}
|
|
2648
|
+
if (parts.length === 0) return void 0;
|
|
2649
|
+
parts.push(tok.text.slice(start));
|
|
2650
|
+
return parts.map((text) => ({
|
|
2651
|
+
...tok,
|
|
2652
|
+
text,
|
|
2653
|
+
widthPt: tok.font.measure.textWidthPt(text, tok.fontSizePt)
|
|
2654
|
+
}));
|
|
2655
|
+
}
|
|
1558
2656
|
function splitToWidth(tok, widthPt) {
|
|
1559
2657
|
if (tok.kind !== "text") return void 0;
|
|
1560
2658
|
const chars = [...tok.text];
|
|
@@ -1585,18 +2683,19 @@ function splitToWidth(tok, widthPt) {
|
|
|
1585
2683
|
}
|
|
1586
2684
|
/** The fewest characters a mid-word break may leave on a line. */
|
|
1587
2685
|
var MIN_SPLIT_CHARS = 6;
|
|
1588
|
-
function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved, profile) {
|
|
2686
|
+
function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved, profile, allowEmpty = false) {
|
|
1589
2687
|
let st = start;
|
|
1590
2688
|
let et = breakIdx;
|
|
1591
2689
|
while (st < et && (entries[st].token === null || !isFirst && entries[st].token?.isSpace === true)) st++;
|
|
1592
2690
|
const paints = (t) => t !== null && t !== void 0 && t.kind === "text" && (t.resolvedRun.underline !== "none" || t.resolvedRun.strike);
|
|
1593
2691
|
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;
|
|
2692
|
+
if (st >= et && !allowEmpty) return null;
|
|
1595
2693
|
const lineTokens = [];
|
|
1596
2694
|
for (let i = st; i < et; i++) {
|
|
1597
2695
|
const ft = entries[i].token;
|
|
1598
2696
|
if (ft) lineTokens.push(ft);
|
|
1599
2697
|
}
|
|
2698
|
+
resolveTabs(lineTokens, resolved, isFirst, availableWidthPt);
|
|
1600
2699
|
const hyphenWidth = entries[breakIdx]?.hyphenWidthPt;
|
|
1601
2700
|
if (hyphenWidth !== void 0) {
|
|
1602
2701
|
const lastIdx = lineTokens.length - 1;
|
|
@@ -1616,8 +2715,10 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
|
|
|
1616
2715
|
let metricAscent = 0;
|
|
1617
2716
|
let metricDescent = 0;
|
|
1618
2717
|
let metricLineGap = 0;
|
|
2718
|
+
const prints = lineTokens.some((t) => !t.isSpace);
|
|
1619
2719
|
for (const t of lineTokens) {
|
|
1620
2720
|
contentWidth += t.widthPt;
|
|
2721
|
+
if (prints && t.isSpace) continue;
|
|
1621
2722
|
const sz = tokenLineSize(t);
|
|
1622
2723
|
if (sz > maxSize) maxSize = sz;
|
|
1623
2724
|
if (t.kind === "math") {
|
|
@@ -1647,10 +2748,26 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
|
|
|
1647
2748
|
} : {}
|
|
1648
2749
|
};
|
|
1649
2750
|
}
|
|
1650
|
-
function
|
|
2751
|
+
function withLeadingTabWidths(tokens, resolved, firstLineWidth) {
|
|
2752
|
+
const first = tokens[0];
|
|
2753
|
+
if (!(first?.kind === "text" && first.tab === true)) return tokens;
|
|
2754
|
+
const probe = tokens.map((t) => ({ ...t }));
|
|
2755
|
+
resolveTabs(probe, resolved, true, firstLineWidth);
|
|
2756
|
+
const out = tokens.slice();
|
|
2757
|
+
for (let i = 0; i < out.length; i++) {
|
|
2758
|
+
const tok = out[i];
|
|
2759
|
+
if (!(tok.kind === "text" && tok.tab === true)) break;
|
|
2760
|
+
out[i] = {
|
|
2761
|
+
...tok,
|
|
2762
|
+
widthPt: probe[i].widthPt
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
return out;
|
|
2766
|
+
}
|
|
2767
|
+
function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile, noExpandShiftReturn, lineWidths) {
|
|
1651
2768
|
if (tokens.length === 0) return [];
|
|
1652
2769
|
const widths = lineWidths ?? [firstLineWidth, otherWidth];
|
|
1653
|
-
const entries = paragraphItemStream(tokens, hyphenator, Math.max(0, ...widths));
|
|
2770
|
+
const entries = paragraphItemStream(withLeadingTabWidths(tokens, resolved, widths[0] ?? firstLineWidth), hyphenator, Math.max(0, ...widths));
|
|
1654
2771
|
const items = entries.map((e) => e.item);
|
|
1655
2772
|
const breaks = profile === "ream" ? breakLines(items, widths).breaks : greedyBreakLines(items, widths);
|
|
1656
2773
|
const buildLines = (at) => {
|
|
@@ -1658,10 +2775,13 @@ function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile,
|
|
|
1658
2775
|
let start = 0;
|
|
1659
2776
|
let isFirst = true;
|
|
1660
2777
|
let lineIdx = 0;
|
|
1661
|
-
for (const breakIdx of at) {
|
|
2778
|
+
for (const [i, breakIdx] of at.entries()) {
|
|
1662
2779
|
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(
|
|
2780
|
+
const line = lineFromRange(entries, start, breakIdx, width, isFirst, resolved, profile, i < at.length - 1);
|
|
2781
|
+
if (line) out.push(noExpandShiftReturn && entries[breakIdx]?.softBreak ? {
|
|
2782
|
+
...line,
|
|
2783
|
+
noJustify: true
|
|
2784
|
+
} : line);
|
|
1665
2785
|
start = breakIdx + 1;
|
|
1666
2786
|
isFirst = false;
|
|
1667
2787
|
lineIdx++;
|
|
@@ -1695,11 +2815,17 @@ function fontLeadingPt(parsed, sizePt, profile) {
|
|
|
1695
2815
|
};
|
|
1696
2816
|
}
|
|
1697
2817
|
function computeLineHeight(line, p) {
|
|
2818
|
+
const natural = naturalLineHeight(line, p);
|
|
2819
|
+
const pitch = p.gridLinePitchPt;
|
|
2820
|
+
if (pitch !== void 0 && pitch > 0) return Math.ceil(natural / pitch - 1e-6) * pitch;
|
|
2821
|
+
return natural;
|
|
2822
|
+
}
|
|
2823
|
+
function naturalLineHeight(line, p) {
|
|
1698
2824
|
const fontSize = line.maxFontSizePt || 12;
|
|
1699
2825
|
const natural = line.metricHeightPt ?? fontSize * 1.2;
|
|
1700
2826
|
const mathH = (line.mathAscentPt ?? 0) + (line.mathDescentPt ?? 0);
|
|
1701
2827
|
const mathNeed = mathH > 0 ? mathH * 1.05 : 0;
|
|
1702
|
-
if (p.spacingLineRule === "exact" && p.spacingLine
|
|
2828
|
+
if (p.spacingLineRule === "exact" && p.spacingLine >= 0) return Math.max(p.spacingLine, mathNeed);
|
|
1703
2829
|
if (p.spacingLineRule === "atLeast" && p.spacingLine > 0) return Math.max(natural, p.spacingLine, mathNeed);
|
|
1704
2830
|
const lineTwips = Math.round(p.spacingLine * 20);
|
|
1705
2831
|
const multiple = lineTwips > 0 ? lineTwips / 240 : 1;
|
|
@@ -1710,6 +2836,37 @@ function lineDescent(line) {
|
|
|
1710
2836
|
const base = line.metricDescentPt ?? fs * .2;
|
|
1711
2837
|
return Math.max(base, line.mathDescentPt ?? 0);
|
|
1712
2838
|
}
|
|
2839
|
+
/**
|
|
2840
|
+
* §17.3.1.33 — how far the baseline stands above the BOTTOM of the line box,
|
|
2841
|
+
* which is what says where the extra height of a spaced-out line goes.
|
|
2842
|
+
*
|
|
2843
|
+
* A line given a fixed height (`exact`, `atLeast`) hangs from the bottom of its
|
|
2844
|
+
* box: the space a taller `w:line` asks for opens ABOVE the text, which is what
|
|
2845
|
+
* makes "at least 24pt" push a heading down its own line.
|
|
2846
|
+
*
|
|
2847
|
+
* `auto` — Word's "Multiple", the 1.15 every modern template sets — scales the
|
|
2848
|
+
* line instead, and the space it gains falls BELOW: the first line of a spaced
|
|
2849
|
+
* paragraph starts exactly where a single-spaced one does. Read the other way
|
|
2850
|
+
* round, fdo80800.docx's 1.5-line cells sat 6.6pt under the plain cell beside
|
|
2851
|
+
* them, where Word and LibreOffice write all three on one baseline.
|
|
2852
|
+
*
|
|
2853
|
+
* §17.6.5 — the extra a document GRID adds is not the paragraph's at all, and
|
|
2854
|
+
* is split evenly: the line is centred on its grid row.
|
|
2855
|
+
*
|
|
2856
|
+
* @param line The laid-out line.
|
|
2857
|
+
* @param p The owning paragraph's resolved properties.
|
|
2858
|
+
* @returns The distance from the line box's bottom edge up to the baseline.
|
|
2859
|
+
*/
|
|
2860
|
+
function lineBaselineOffset(line, p) {
|
|
2861
|
+
const descent = lineDescent(line);
|
|
2862
|
+
const natural = naturalLineHeight(line, p);
|
|
2863
|
+
const singled = naturalLineHeight(line, {
|
|
2864
|
+
...p,
|
|
2865
|
+
spacingLineRule: "auto",
|
|
2866
|
+
spacingLine: pt(0)
|
|
2867
|
+
});
|
|
2868
|
+
return descent + (p.spacingLineRule === "auto" ? Math.max(0, natural - singled) : 0) + (computeLineHeight(line, p) - natural) / 2;
|
|
2869
|
+
}
|
|
1713
2870
|
function alignmentOffset(alignment, lineWidth, available) {
|
|
1714
2871
|
if (alignment === "right") return Math.max(0, available - lineWidth);
|
|
1715
2872
|
if (alignment === "center") return Math.max(0, (available - lineWidth) / 2);
|
|
@@ -1724,7 +2881,7 @@ function layoutTableBlock(table, options, fontResources, imageResources, content
|
|
|
1724
2881
|
for (let r = 0; r < table.rows.length; r++) {
|
|
1725
2882
|
const rl = layoutTableRow(table.rows[r], r, table.rows.length, colCount, table.properties, columnWidthsPt, options, fontResources, imageResources, mergeRoles[r] ?? [], aboveBordersByCol);
|
|
1726
2883
|
const nextAbove = [];
|
|
1727
|
-
let ci = 0;
|
|
2884
|
+
let ci = table.rows[r].properties.gridBefore ?? 0;
|
|
1728
2885
|
for (const cell of table.rows[r].cells) {
|
|
1729
2886
|
const span = Math.max(1, cell.properties.colSpan ?? 1);
|
|
1730
2887
|
for (let k = 0; k < span; k++) nextAbove[ci + k] = cell.properties.borders;
|
|
@@ -1738,17 +2895,46 @@ function layoutTableBlock(table, options, fontResources, imageResources, content
|
|
|
1738
2895
|
...rp.pageBreakBefore ? { breakBefore: true } : {}
|
|
1739
2896
|
} : rl);
|
|
1740
2897
|
}
|
|
2898
|
+
growRowsForMerges(rows);
|
|
1741
2899
|
const heightPt = rows.reduce((s, r) => s + r.heightPt, 0);
|
|
1742
2900
|
const totalWidthPt = columnWidthsPt.reduce((s, w) => s + w, 0);
|
|
2901
|
+
const xOffsetPt = (table.properties.indentPt ?? 0) + tableXOffset(table.properties.alignment, contentWidth, totalWidthPt);
|
|
1743
2902
|
return {
|
|
1744
2903
|
kind: "table",
|
|
2904
|
+
...table.properties.float ? { float: table.properties.float } : {},
|
|
1745
2905
|
rows,
|
|
1746
2906
|
heightPt,
|
|
1747
2907
|
totalWidthPt,
|
|
1748
2908
|
colCount,
|
|
1749
|
-
xOffsetPt
|
|
2909
|
+
xOffsetPt
|
|
1750
2910
|
};
|
|
1751
2911
|
}
|
|
2912
|
+
/**
|
|
2913
|
+
* §17.4.85 `w:vMerge` — make every vertical merge fit. Each row is already as
|
|
2914
|
+
* tall as its own (unmerged) cells; a merge that needs more than its rows add
|
|
2915
|
+
* up to grows the LAST row of the run, which is where Word puts the slack.
|
|
2916
|
+
*
|
|
2917
|
+
* @param rows The table's laid-out rows, adjusted in place.
|
|
2918
|
+
*/
|
|
2919
|
+
function growRowsForMerges(rows) {
|
|
2920
|
+
for (let r = 0; r < rows.length; r++) for (const cell of rows[r].cells) {
|
|
2921
|
+
if (cell.mergeRole !== "start") continue;
|
|
2922
|
+
let last = r;
|
|
2923
|
+
for (let k = r + 1; k < rows.length; k++) {
|
|
2924
|
+
const below = rows[k].cells.find((c) => c.colStart === cell.colStart);
|
|
2925
|
+
if (!below || below.mergeRole !== "middle" && below.mergeRole !== "end") break;
|
|
2926
|
+
last = k;
|
|
2927
|
+
if (below.mergeRole === "end") break;
|
|
2928
|
+
}
|
|
2929
|
+
let have = 0;
|
|
2930
|
+
for (let k = r; k <= last; k++) have += rows[k].heightPt;
|
|
2931
|
+
const deficit = cell.totalHeightPt - have;
|
|
2932
|
+
if (deficit > .01) rows[last] = {
|
|
2933
|
+
...rows[last],
|
|
2934
|
+
heightPt: rows[last].heightPt + deficit
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
1752
2938
|
function tableXOffset(alignment, contentWidth, totalWidthPt) {
|
|
1753
2939
|
const slack = contentWidth - totalWidthPt;
|
|
1754
2940
|
if (slack <= 0 || !alignment || alignment === "left") return 0;
|
|
@@ -1757,15 +2943,18 @@ function tableXOffset(alignment, contentWidth, totalWidthPt) {
|
|
|
1757
2943
|
function computeColumnWidths(table, options, fontResources, contentWidth) {
|
|
1758
2944
|
let colCount = table.grid.length;
|
|
1759
2945
|
for (const row of table.rows) {
|
|
1760
|
-
let rowCols = 0;
|
|
2946
|
+
let rowCols = row.properties.gridBefore ?? 0;
|
|
1761
2947
|
for (const cell of row.cells) rowCols += Math.max(1, cell.properties.colSpan ?? 1);
|
|
1762
2948
|
if (rowCols > colCount) colCount = rowCols;
|
|
1763
2949
|
}
|
|
1764
2950
|
if (colCount === 0) return [];
|
|
1765
|
-
|
|
2951
|
+
const gridShort = gridColTwips(table).length < colCount;
|
|
2952
|
+
const declared = declaredColumnWidths(table, colCount, contentWidth, gridShort);
|
|
2953
|
+
if (declared) return declared;
|
|
2954
|
+
if (gridColTwips(table).some((w) => w > 0)) return gridWidthsScaled(table, contentWidth, colCount);
|
|
1766
2955
|
const colNaturalWidths = new Array(colCount).fill(0);
|
|
1767
2956
|
for (const row of table.rows) {
|
|
1768
|
-
let colIdx = 0;
|
|
2957
|
+
let colIdx = row.properties.gridBefore ?? 0;
|
|
1769
2958
|
for (let i = 0; i < row.cells.length; i++) {
|
|
1770
2959
|
const cell = row.cells[i];
|
|
1771
2960
|
const span = Math.max(1, cell.properties.colSpan ?? 1);
|
|
@@ -1798,6 +2987,81 @@ function computeColumnWidths(table, options, fontResources, contentWidth) {
|
|
|
1798
2987
|
function gridColTwips(table) {
|
|
1799
2988
|
return table.grid.map((w) => Math.round(w * 20));
|
|
1800
2989
|
}
|
|
2990
|
+
/**
|
|
2991
|
+
* §17.4.53 — column widths taken from the cells' own `w:tcW` for a table laid
|
|
2992
|
+
* out `fixed`. Each cell shares its preferred width over the columns it spans
|
|
2993
|
+
* and the widest claim wins; a column nobody declares keeps its grid share.
|
|
2994
|
+
* Returns `undefined` (⇒ the grid decides) unless the table says `fixed` and
|
|
2995
|
+
* enough of it is declared to trust.
|
|
2996
|
+
*/
|
|
2997
|
+
function declaredColumnWidths(table, colCount, contentWidth, gridShort = false) {
|
|
2998
|
+
if (table.properties.layout !== "fixed" && !gridShort) return void 0;
|
|
2999
|
+
const target = totalTableTarget(table, contentWidth);
|
|
3000
|
+
const declaredWidth = (cell) => {
|
|
3001
|
+
const p = cell.properties;
|
|
3002
|
+
if (p.widthFraction !== void 0) return p.widthFraction * target;
|
|
3003
|
+
if (p.width !== void 0 && p.widthType !== "auto" && p.widthType !== "nil") return p.width;
|
|
3004
|
+
};
|
|
3005
|
+
const claimed = new Array(colCount).fill(0);
|
|
3006
|
+
const spanning = [];
|
|
3007
|
+
let anyDeclared = false;
|
|
3008
|
+
let reach = 0;
|
|
3009
|
+
for (const row of table.rows) {
|
|
3010
|
+
if (row.cells.length < 2) continue;
|
|
3011
|
+
let span = row.properties.gridBefore ?? 0;
|
|
3012
|
+
for (const cell of row.cells) span += Math.max(1, cell.properties.colSpan ?? 1);
|
|
3013
|
+
if (span > reach) reach = span;
|
|
3014
|
+
}
|
|
3015
|
+
const phantomFrom = reach > 0 && reach < colCount ? reach : colCount;
|
|
3016
|
+
for (const row of table.rows) {
|
|
3017
|
+
let colIdx = row.properties.gridBefore ?? 0;
|
|
3018
|
+
for (const cell of row.cells) {
|
|
3019
|
+
const span = Math.max(1, cell.properties.colSpan ?? 1);
|
|
3020
|
+
const widthPt = declaredWidth(cell);
|
|
3021
|
+
if (widthPt !== void 0 && widthPt > 0) {
|
|
3022
|
+
anyDeclared = true;
|
|
3023
|
+
if (span === 1) {
|
|
3024
|
+
if (widthPt > claimed[colIdx]) claimed[colIdx] = widthPt;
|
|
3025
|
+
} else spanning.push({
|
|
3026
|
+
start: colIdx,
|
|
3027
|
+
span,
|
|
3028
|
+
widthPt
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
colIdx += span;
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
if (!anyDeclared) return void 0;
|
|
3035
|
+
for (const s of spanning) {
|
|
3036
|
+
const end = Math.min(s.start + s.span, phantomFrom);
|
|
3037
|
+
let have = 0;
|
|
3038
|
+
const blank = [];
|
|
3039
|
+
for (let i = s.start; i < end; i++) {
|
|
3040
|
+
have += claimed[i];
|
|
3041
|
+
if (claimed[i] === 0) blank.push(i);
|
|
3042
|
+
}
|
|
3043
|
+
const deficit = s.widthPt - have;
|
|
3044
|
+
if (deficit <= 0) continue;
|
|
3045
|
+
if (blank.length > 0) {
|
|
3046
|
+
const each = deficit / blank.length;
|
|
3047
|
+
for (const i of blank) claimed[i] = each;
|
|
3048
|
+
} else if (have > 0) for (let i = s.start; i < end; i++) claimed[i] = claimed[i] * (s.widthPt / have);
|
|
3049
|
+
}
|
|
3050
|
+
const gridTwips = gridColTwips(table);
|
|
3051
|
+
const rest = [];
|
|
3052
|
+
let claimedTotal = 0;
|
|
3053
|
+
for (let i = 0; i < phantomFrom; i++) if (claimed[i] > 0) claimedTotal += claimed[i];
|
|
3054
|
+
else rest.push(i);
|
|
3055
|
+
if (rest.length > 0) {
|
|
3056
|
+
const leftover = Math.max(0, target - claimedTotal);
|
|
3057
|
+
const restGrid = rest.reduce((s, i) => s + (gridTwips[i] ?? 0), 0);
|
|
3058
|
+
for (const i of rest) claimed[i] = restGrid > 0 ? (gridTwips[i] ?? 0) / restGrid * leftover : leftover / rest.length;
|
|
3059
|
+
}
|
|
3060
|
+
const total = claimed.reduce((s, w) => s + w, 0);
|
|
3061
|
+
if (total <= 0) return void 0;
|
|
3062
|
+
const scale = total > target ? target / total : 1;
|
|
3063
|
+
return claimed.map((w) => w * scale);
|
|
3064
|
+
}
|
|
1801
3065
|
function gridWidthsScaled(table, contentWidth, colCount) {
|
|
1802
3066
|
const gridTwips = gridColTwips(table);
|
|
1803
3067
|
const totalGridTwips = gridTwips.reduce((s, w) => s + w, 0);
|
|
@@ -1860,8 +3124,9 @@ function aboveOfSpan(aboveBordersByCol, colIdx, span) {
|
|
|
1860
3124
|
function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidthsPt, options, fontResources, imageResources, rowMergeRoles, aboveBordersByCol) {
|
|
1861
3125
|
const cells = [];
|
|
1862
3126
|
const columnXOffsets = [];
|
|
1863
|
-
|
|
1864
|
-
let
|
|
3127
|
+
const skip = Math.min(Math.max(0, row.properties.gridBefore ?? 0), columnWidthsPt.length);
|
|
3128
|
+
let cursorX = columnWidthsPt.slice(0, skip).reduce((sum, w) => sum + w, 0);
|
|
3129
|
+
let colIdx = skip;
|
|
1865
3130
|
for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
|
|
1866
3131
|
const cell = row.cells[cellIdx];
|
|
1867
3132
|
const span = Math.max(1, cell.properties.colSpan ?? 1);
|
|
@@ -1882,7 +3147,10 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
|
|
|
1882
3147
|
colIdx += span;
|
|
1883
3148
|
}
|
|
1884
3149
|
let heightPt = 0;
|
|
1885
|
-
for (const c of cells)
|
|
3150
|
+
for (const c of cells) {
|
|
3151
|
+
if (c.mergeRole === "start") continue;
|
|
3152
|
+
if (c.totalHeightPt > heightPt) heightPt = c.totalHeightPt;
|
|
3153
|
+
}
|
|
1886
3154
|
if (row.properties.height && row.properties.heightRule === "exact") {
|
|
1887
3155
|
heightPt = row.properties.height;
|
|
1888
3156
|
return {
|
|
@@ -1930,6 +3198,30 @@ function clipCellToHeight(cell, heightPt) {
|
|
|
1930
3198
|
contentHeightPt: used
|
|
1931
3199
|
};
|
|
1932
3200
|
}
|
|
3201
|
+
/**
|
|
3202
|
+
* The one run a picture BLOCK is made of, so a context that lays out paragraphs
|
|
3203
|
+
* (a table cell) can draw it without a block path of its own.
|
|
3204
|
+
*
|
|
3205
|
+
* @param image The image block.
|
|
3206
|
+
* @returns A run carrying it as an inline picture.
|
|
3207
|
+
*/
|
|
3208
|
+
function imageRun(image) {
|
|
3209
|
+
return {
|
|
3210
|
+
text: "",
|
|
3211
|
+
properties: {},
|
|
3212
|
+
inlineImage: {
|
|
3213
|
+
...image.resource ? { resource: image.resource } : {},
|
|
3214
|
+
...image.outline ? { outline: image.outline } : {},
|
|
3215
|
+
width: image.width,
|
|
3216
|
+
height: image.height,
|
|
3217
|
+
...image.crop ? { crop: image.crop } : {},
|
|
3218
|
+
...image.rotation60k ? { rotation60k: image.rotation60k } : {},
|
|
3219
|
+
...image.flipH ? { flipH: true } : {},
|
|
3220
|
+
...image.flipV ? { flipV: true } : {},
|
|
3221
|
+
...image.effectExtent ? { effectExtent: image.effectExtent } : {}
|
|
3222
|
+
}
|
|
3223
|
+
};
|
|
3224
|
+
}
|
|
1933
3225
|
function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imageResources, rowIdx, colStart, colSpan, rowCount, colCount, mergeRole, leftNeighborBorders, aboveNeighborBorders) {
|
|
1934
3226
|
const cellMar = cell.properties.margins;
|
|
1935
3227
|
const tableMar = tableProps.defaultCellMargins;
|
|
@@ -1942,8 +3234,18 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
1942
3234
|
const innerWidth = Math.max(1, widthPt - padLeftPt - padRightPt);
|
|
1943
3235
|
const lines = [];
|
|
1944
3236
|
const nestedTables = [];
|
|
3237
|
+
const cellShapes = [];
|
|
3238
|
+
const lineGaps = /* @__PURE__ */ new Map();
|
|
1945
3239
|
let contentHeightPt = 0;
|
|
1946
3240
|
let clipped = false;
|
|
3241
|
+
let pendingGapPt = 0;
|
|
3242
|
+
const openParagraph = (spacingBeforePt) => {
|
|
3243
|
+
const gap = pendingGapPt + spacingBeforePt;
|
|
3244
|
+
pendingGapPt = 0;
|
|
3245
|
+
if (gap <= 0) return;
|
|
3246
|
+
lineGaps.set(lines.length, (lineGaps.get(lines.length) ?? 0) + gap);
|
|
3247
|
+
contentHeightPt += gap;
|
|
3248
|
+
};
|
|
1947
3249
|
if (mergeRole !== "middle" && mergeRole !== "end") {
|
|
1948
3250
|
for (const el of cell.content) if (el.kind === "paragraph") {
|
|
1949
3251
|
const noWrap = cell.properties.noWrap === true;
|
|
@@ -1951,16 +3253,43 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
1951
3253
|
const cutAt = widthPt - padLeftPt;
|
|
1952
3254
|
if (noWrap && (block.lines[0]?.contentWidthPt ?? 0) > cutAt) clipped = true;
|
|
1953
3255
|
const keep = noWrap ? clipLineToWidth(block.lines, cutAt, cell.properties.hashOnOverflow === true) : block.lines;
|
|
3256
|
+
const markOnly = cell.properties.hideMark === true && el.paragraph.runs.every((r) => r.text === "");
|
|
3257
|
+
if (!markOnly) openParagraph(block.spacingBeforePt);
|
|
1954
3258
|
for (const line of keep) {
|
|
1955
3259
|
lines.push(line);
|
|
1956
|
-
contentHeightPt += computeLineHeight(line, block.resolved);
|
|
3260
|
+
if (!markOnly) contentHeightPt += computeLineHeight(line, block.resolved);
|
|
1957
3261
|
}
|
|
1958
|
-
|
|
3262
|
+
if (!markOnly) pendingGapPt = block.spacingAfterPt;
|
|
1959
3263
|
} else if (el.kind === "table") {
|
|
1960
3264
|
const nested = layoutTableBlock(el.table, options, fontResources, imageResources, innerWidth);
|
|
1961
|
-
nestedTables.push(
|
|
3265
|
+
nestedTables.push({
|
|
3266
|
+
block: nested,
|
|
3267
|
+
afterLine: lines.length
|
|
3268
|
+
});
|
|
1962
3269
|
contentHeightPt += nested.heightPt;
|
|
3270
|
+
} else if (el.kind === "image" && !isOutOfFlowFloat(el.image.float)) {
|
|
3271
|
+
const block = layoutParagraphBlock({
|
|
3272
|
+
properties: el.image.paragraphProperties,
|
|
3273
|
+
runs: [imageRun(el.image)]
|
|
3274
|
+
}, options, fontResources, imageResources, innerWidth);
|
|
3275
|
+
openParagraph(block.spacingBeforePt);
|
|
3276
|
+
for (const line of block.lines) {
|
|
3277
|
+
lines.push(line);
|
|
3278
|
+
contentHeightPt += computeLineHeight(line, block.resolved);
|
|
3279
|
+
}
|
|
3280
|
+
contentHeightPt += block.spacingAfterPt;
|
|
3281
|
+
pendingGapPt = block.spacingAfterPt;
|
|
3282
|
+
} else if (el.kind === "shape" && !isOutOfFlowFloat(el.shape.float)) {
|
|
3283
|
+
const block = layoutShapeBlock(el.shape, options, fontResources, imageResources, innerWidth);
|
|
3284
|
+
openParagraph(block.spacingBeforePt);
|
|
3285
|
+
cellShapes.push({
|
|
3286
|
+
block,
|
|
3287
|
+
afterLine: lines.length
|
|
3288
|
+
});
|
|
3289
|
+
contentHeightPt += block.heightPt;
|
|
3290
|
+
pendingGapPt = block.spacingAfterPt;
|
|
1963
3291
|
}
|
|
3292
|
+
contentHeightPt += pendingGapPt;
|
|
1964
3293
|
}
|
|
1965
3294
|
const colEnd = colStart + colSpan - 1;
|
|
1966
3295
|
const borders = resolveCellBorders(cell.properties.borders, tableProps.borders, rowIdx, colStart, colEnd, rowCount, colCount, mergeRole, leftNeighborBorders, aboveNeighborBorders);
|
|
@@ -1977,7 +3306,9 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
1977
3306
|
...cell.properties.icon ? { icon: cell.properties.icon } : {},
|
|
1978
3307
|
...cell.properties.sparkline ? { sparkline: cell.properties.sparkline } : {},
|
|
1979
3308
|
lines,
|
|
3309
|
+
...lineGaps.size > 0 ? { lineGaps } : {},
|
|
1980
3310
|
...nestedTables.length > 0 ? { nestedTables } : {},
|
|
3311
|
+
...cellShapes.length > 0 ? { shapes: cellShapes } : {},
|
|
1981
3312
|
contentHeightPt,
|
|
1982
3313
|
totalHeightPt,
|
|
1983
3314
|
...cell.properties.verticalAlign ? { verticalAlign: cell.properties.verticalAlign } : {},
|
|
@@ -1992,6 +3323,7 @@ var BORDER_STYLE_RANK = {
|
|
|
1992
3323
|
thick: 4,
|
|
1993
3324
|
single: 3,
|
|
1994
3325
|
dashed: 2,
|
|
3326
|
+
dashSmallGap: 2,
|
|
1995
3327
|
dashDot: 2,
|
|
1996
3328
|
dashDotDot: 2,
|
|
1997
3329
|
dotted: 1,
|
|
@@ -2099,7 +3431,7 @@ function listItemParagraphNode(builder, stack, list, wantLbl) {
|
|
|
2099
3431
|
* Push order into `current`/`pages` IS the byte order of the emitted PDF, so the
|
|
2100
3432
|
* bodies move unchanged.
|
|
2101
3433
|
*/
|
|
2102
|
-
var PageAssembler = class {
|
|
3434
|
+
var PageAssembler = class PageAssembler {
|
|
2103
3435
|
/**
|
|
2104
3436
|
* @param sectionCtxs The per-section render contexts, in order; assembly
|
|
2105
3437
|
* starts on the first section's first page.
|
|
@@ -2109,20 +3441,29 @@ var PageAssembler = class {
|
|
|
2109
3441
|
* `undefined` when there are none.
|
|
2110
3442
|
* @param bookmarkPositions Out-param: bookmark name → its GoTo destination.
|
|
2111
3443
|
*/
|
|
2112
|
-
constructor(sectionCtxs, builder, notes, bookmarkPositions) {
|
|
3444
|
+
constructor(sectionCtxs, builder, notes, bookmarkPositions, backgroundImageName) {
|
|
2113
3445
|
this.sectionCtxs = sectionCtxs;
|
|
2114
3446
|
this.builder = builder;
|
|
2115
3447
|
this.notes = notes;
|
|
2116
3448
|
this.bookmarkPositions = bookmarkPositions;
|
|
3449
|
+
this.backgroundImageName = backgroundImageName;
|
|
2117
3450
|
this.ctx = sectionCtxs[0];
|
|
3451
|
+
this.pageStartCtx = this.ctx;
|
|
3452
|
+
this.pageNumber = this.ctx.properties.pageNumberStart ?? 1;
|
|
2118
3453
|
this.cursorY = this.ctx.pageHeight - this.ctx.marginTop;
|
|
2119
3454
|
this.colStartY = this.cursorY;
|
|
3455
|
+
this.bandTopY = this.cursorY;
|
|
2120
3456
|
}
|
|
2121
3457
|
pages = [];
|
|
2122
3458
|
ctx;
|
|
2123
3459
|
secIdx = 0;
|
|
2124
3460
|
pageInSection = 0;
|
|
2125
3461
|
globalPageIdx = 0;
|
|
3462
|
+
/**
|
|
3463
|
+
* §17.6.12 — the number the NEXT page prints for a PAGE field. Counts up with
|
|
3464
|
+
* the pages and restarts wherever a section names a `w:pgNumType w:start`.
|
|
3465
|
+
*/
|
|
3466
|
+
pageNumber = 1;
|
|
2126
3467
|
current = [];
|
|
2127
3468
|
pendingPageBreak = false;
|
|
2128
3469
|
cursorY;
|
|
@@ -2136,6 +3477,12 @@ var PageAssembler = class {
|
|
|
2136
3477
|
colStartLen = 0;
|
|
2137
3478
|
/** The cursor's y at the top of the current column — see {@link colHasContent}. */
|
|
2138
3479
|
colStartY;
|
|
3480
|
+
/**
|
|
3481
|
+
* Where this page's column band begins. The top margin on an ordinary page;
|
|
3482
|
+
* further down when a §17.6.22 `continuous` section started mid-page, whose
|
|
3483
|
+
* columns run beside each other from there, not from the paper's top.
|
|
3484
|
+
*/
|
|
3485
|
+
bandTopY;
|
|
2139
3486
|
/** The current column's left edge (`marginLeft` plus the column x-offset). */
|
|
2140
3487
|
colLeft = () => this.ctx.marginLeft + (this.ctx.columns?.[this.colIdx]?.xOffsetPt ?? 0);
|
|
2141
3488
|
/** The current column's width (the section content width when single-column). */
|
|
@@ -2167,15 +3514,118 @@ var PageAssembler = class {
|
|
|
2167
3514
|
if (this.ctx.columns && this.colIdx + 1 < this.ctx.columns.length) {
|
|
2168
3515
|
this.colIdx++;
|
|
2169
3516
|
this.colStartLen = this.current.length;
|
|
2170
|
-
this.cursorY = this.
|
|
3517
|
+
this.cursorY = this.bandTopY;
|
|
2171
3518
|
this.colStartY = this.cursorY;
|
|
3519
|
+
const isLast = this.colIdx + 1 >= this.ctx.columns.length;
|
|
3520
|
+
this.balanceBottomY = this.balanceHeightPt > 0 && !isLast ? this.cursorY - this.balanceHeightPt : void 0;
|
|
2172
3521
|
} else this.flushPage();
|
|
2173
3522
|
};
|
|
2174
3523
|
/**
|
|
3524
|
+
* §17.6.8 — the running line count for the margin numbers. Reset by the
|
|
3525
|
+
* section's own `w:restart` rule.
|
|
3526
|
+
*/
|
|
3527
|
+
lineNumber = 0;
|
|
3528
|
+
/**
|
|
3529
|
+
* §17.6.8 — the margin number for the body line just placed, if the section
|
|
3530
|
+
* asks for one at this count. Pushed at the line's own baseline, right-
|
|
3531
|
+
* aligned a quarter inch off the text.
|
|
3532
|
+
*
|
|
3533
|
+
* @param baselineY The line's baseline, in page (y-down) coordinates.
|
|
3534
|
+
* @param line The line being numbered — its font draws the digits.
|
|
3535
|
+
*/
|
|
3536
|
+
emitLineNumber = (baselineY, line) => {
|
|
3537
|
+
const ln = this.ctx.properties.lineNumbering;
|
|
3538
|
+
if (!ln) return;
|
|
3539
|
+
this.lineNumber++;
|
|
3540
|
+
const n = ln.start + this.lineNumber - 1;
|
|
3541
|
+
if ((n - ln.start + 1) % ln.countBy !== 0) return;
|
|
3542
|
+
const token = line.tokens.find((t) => t.kind === "text");
|
|
3543
|
+
if (!token) return;
|
|
3544
|
+
const text = String(n);
|
|
3545
|
+
const sizePt = token.fontSizePt;
|
|
3546
|
+
const widthPt = token.font.measure.textWidthPt(text, sizePt);
|
|
3547
|
+
const digits = {
|
|
3548
|
+
kind: "text",
|
|
3549
|
+
text,
|
|
3550
|
+
isSpace: false,
|
|
3551
|
+
resolvedRun: token.resolvedRun,
|
|
3552
|
+
font: token.font,
|
|
3553
|
+
fontSizePt: sizePt,
|
|
3554
|
+
widthPt,
|
|
3555
|
+
bidiLevel: 0
|
|
3556
|
+
};
|
|
3557
|
+
const numbered = {
|
|
3558
|
+
...line,
|
|
3559
|
+
tokens: [digits],
|
|
3560
|
+
contentWidthPt: widthPt
|
|
3561
|
+
};
|
|
3562
|
+
this.current.push({
|
|
3563
|
+
type: "line",
|
|
3564
|
+
line: numbered,
|
|
3565
|
+
originX: pt(this.colLeft() - (ln.distancePt ?? LINE_NUMBER_GAP_PT) - widthPt),
|
|
3566
|
+
baselineY: pt(baselineY)
|
|
3567
|
+
});
|
|
3568
|
+
};
|
|
3569
|
+
/**
|
|
3570
|
+
* §17.6.12 — a section that names its own first page number restarts the
|
|
3571
|
+
* count there; one that names none carries on from the section before.
|
|
3572
|
+
*
|
|
3573
|
+
* @param next The section being entered.
|
|
3574
|
+
*/
|
|
3575
|
+
restartPageNumbers = (next) => {
|
|
3576
|
+
const start = next.properties.pageNumberStart;
|
|
3577
|
+
if (start !== void 0) this.pageNumber = start;
|
|
3578
|
+
if ((next.properties.lineNumbering?.restart ?? "newPage") !== "continuous") this.lineNumber = 0;
|
|
3579
|
+
};
|
|
3580
|
+
/**
|
|
3581
|
+
* §17.6.22 — start a `continuous` section on the page in hand: its columns
|
|
3582
|
+
* begin where the section does, not at the top of the paper.
|
|
3583
|
+
*
|
|
3584
|
+
* @param next The section to continue into.
|
|
3585
|
+
*/
|
|
3586
|
+
startBandSection = (next, bandHeightPt) => {
|
|
3587
|
+
this.secIdx++;
|
|
3588
|
+
this.ctx = next;
|
|
3589
|
+
this.restartPageNumbers(next);
|
|
3590
|
+
this.colIdx = 0;
|
|
3591
|
+
this.colStartLen = this.current.length;
|
|
3592
|
+
this.cursorY = Math.min(this.cursorY, next.pageHeight - next.marginTop);
|
|
3593
|
+
this.bandTopY = this.cursorY;
|
|
3594
|
+
this.colStartY = this.cursorY;
|
|
3595
|
+
this.beginBalancedBand(bandHeightPt);
|
|
3596
|
+
};
|
|
3597
|
+
/**
|
|
3598
|
+
* §17.6.4 — even the columns of the band that starts at the cursor: each gets
|
|
3599
|
+
* the same share of `bandHeightPt`. A band too tall for the page fills column
|
|
3600
|
+
* after column as before; there is nothing to even out when it will spill
|
|
3601
|
+
* anyway.
|
|
3602
|
+
*
|
|
3603
|
+
* @param bandHeightPt The height of everything the band holds.
|
|
3604
|
+
*/
|
|
3605
|
+
beginBalancedBand = (bandHeightPt) => {
|
|
3606
|
+
const colCount = this.ctx.columns?.length ?? 1;
|
|
3607
|
+
const room = this.cursorY - (this.ctx.marginBottom + this.noteReserve);
|
|
3608
|
+
const share = colCount > 1 ? balancedColumnHeight(bandHeightPt, colCount, room) : 0;
|
|
3609
|
+
this.balanceHeightPt = share;
|
|
3610
|
+
this.balanceBottomY = share > 0 ? this.cursorY - share : void 0;
|
|
3611
|
+
};
|
|
3612
|
+
/**
|
|
2175
3613
|
* §20.4.2.3 out-of-flow drawings (wrap `'none'`): they render at their anchored
|
|
2176
3614
|
* position without moving the cursor. `behindDoc` sinks below the body text,
|
|
2177
3615
|
* everything else above it; both flush with the page.
|
|
2178
3616
|
*/
|
|
3617
|
+
/**
|
|
3618
|
+
* §20.4.2.3 — a float layer, drawn in `relativeHeight` order. A stable sort
|
|
3619
|
+
* by z leaves the ones that state none where the document put them:
|
|
3620
|
+
* dml-rectangle-relsize.docx writes its blue bar first and gives it the
|
|
3621
|
+
* higher z, and drawn in document order the white rectangle covered it.
|
|
3622
|
+
*/
|
|
3623
|
+
/** A float layer that stamps each item it takes with the float's z-order. */
|
|
3624
|
+
static zSink = (target, z) => z === void 0 ? target : { push: (...items) => target.push(...items.map((i) => ({
|
|
3625
|
+
...i,
|
|
3626
|
+
z
|
|
3627
|
+
}))) };
|
|
3628
|
+
static byZ = (items) => items.every((i) => i.z === void 0) ? [...items] : [...items].sort((a, b) => (a.z ?? 0) - (b.z ?? 0));
|
|
2179
3629
|
floatsBehind = [];
|
|
2180
3630
|
floatsFront = [];
|
|
2181
3631
|
/**
|
|
@@ -2183,12 +3633,26 @@ var PageAssembler = class {
|
|
|
2183
3633
|
* must flow around. Page-scoped, like the float graphics.
|
|
2184
3634
|
*/
|
|
2185
3635
|
exclusions = [];
|
|
3636
|
+
/**
|
|
3637
|
+
* Claim the area a side-wrapping float keeps to itself: its own box grown by
|
|
3638
|
+
* the §20.4.2.3 `distT/B/L/R` stand-off the anchor asks for.
|
|
3639
|
+
*/
|
|
3640
|
+
excludeFloat = (f, x, topYUp, w, h) => {
|
|
3641
|
+
const d = f.wrapDist;
|
|
3642
|
+
this.exclusions.push({
|
|
3643
|
+
x0: x - (d?.leftPt ?? 0),
|
|
3644
|
+
x1: x + w + (d?.rightPt ?? 0),
|
|
3645
|
+
topYUp: topYUp + (d?.topPt ?? 0),
|
|
3646
|
+
bottomYUp: topYUp - h - (d?.bottomPt ?? 0)
|
|
3647
|
+
});
|
|
3648
|
+
};
|
|
2186
3649
|
/** The float's left edge on the page, resolved from its horizontal anchor. */
|
|
2187
3650
|
floatX = (f, widthPt) => {
|
|
2188
3651
|
const h = f.posH;
|
|
2189
3652
|
if (!h) return this.colLeft();
|
|
2190
|
-
const
|
|
2191
|
-
const
|
|
3653
|
+
const textRight = this.ctx.marginLeft + this.ctx.contentWidth;
|
|
3654
|
+
const base = h.relativeFrom === "page" || h.relativeFrom === "leftMargin" ? 0 : h.relativeFrom === "column" ? this.colLeft() : h.relativeFrom === "rightMargin" ? textRight : this.ctx.marginLeft;
|
|
3655
|
+
const span = h.relativeFrom === "page" ? this.ctx.pageWidth : h.relativeFrom === "column" ? this.colWidth() : h.relativeFrom === "leftMargin" ? this.ctx.marginLeft : h.relativeFrom === "rightMargin" ? this.ctx.pageWidth - textRight : this.ctx.contentWidth;
|
|
2192
3656
|
if (h.align === "center") return base + (span - widthPt) / 2;
|
|
2193
3657
|
if (h.align === "right") return base + span - widthPt;
|
|
2194
3658
|
return base + (h.offsetPt ?? 0);
|
|
@@ -2197,10 +3661,19 @@ var PageAssembler = class {
|
|
|
2197
3661
|
* The drawing's TOP in the y-up cursor frame. Paragraph/line-relative offsets
|
|
2198
3662
|
* hang off the anchoring paragraph's current position.
|
|
2199
3663
|
*/
|
|
2200
|
-
floatTopYUp = (f) => {
|
|
3664
|
+
floatTopYUp = (f, heightPt = 0) => {
|
|
2201
3665
|
const v = f.posV;
|
|
2202
3666
|
if (!v) return this.cursorY;
|
|
2203
|
-
if (v.
|
|
3667
|
+
if (v.align !== void 0) {
|
|
3668
|
+
const page = v.relativeFrom === "page";
|
|
3669
|
+
const topYUp = page ? this.ctx.pageHeight : this.ctx.pageHeight - this.ctx.marginTop;
|
|
3670
|
+
const bottomYUp = page ? 0 : this.ctx.marginBottom;
|
|
3671
|
+
if (v.align === "top") return topYUp;
|
|
3672
|
+
if (v.align === "bottom") return bottomYUp + heightPt;
|
|
3673
|
+
return bottomYUp + (topYUp - bottomYUp + heightPt) / 2;
|
|
3674
|
+
}
|
|
3675
|
+
if (v.relativeFrom === "page" || v.relativeFrom === "topMargin") return this.ctx.pageHeight - (v.offsetPt ?? 0);
|
|
3676
|
+
if (v.relativeFrom === "bottomMargin") return this.ctx.marginBottom - (v.offsetPt ?? 0);
|
|
2204
3677
|
if (v.relativeFrom === "margin") return this.ctx.pageHeight - this.ctx.marginTop - (v.offsetPt ?? 0);
|
|
2205
3678
|
return this.cursorY - (v.offsetPt ?? 0);
|
|
2206
3679
|
};
|
|
@@ -2219,7 +3692,21 @@ var PageAssembler = class {
|
|
|
2219
3692
|
noteReserve = 0;
|
|
2220
3693
|
placedNotes = /* @__PURE__ */ new Set();
|
|
2221
3694
|
/** The page's usable bottom: the margin plus whatever the notes band has claimed so far. */
|
|
2222
|
-
|
|
3695
|
+
/**
|
|
3696
|
+
* §17.6.4 — where the current column must stop. Normally the page's bottom
|
|
3697
|
+
* margin (plus any footnote reserve); inside a BALANCED band it is the line
|
|
3698
|
+
* the band's share of the height reaches, so the columns come out even.
|
|
3699
|
+
*/
|
|
3700
|
+
bottomLimit = () => Math.max(this.ctx.marginBottom + this.noteReserve, this.balanceBottomY ?? Number.NEGATIVE_INFINITY);
|
|
3701
|
+
/**
|
|
3702
|
+
* §17.6.4 — a `continuous` section of several columns whose content fits the
|
|
3703
|
+
* page is BALANCED: Word evens the columns out rather than filling the first
|
|
3704
|
+
* to the bottom. Set while such a band is being laid out; the height each
|
|
3705
|
+
* column is given is `balanceHeightPt`. IndexFieldFlagF.docx sets its index
|
|
3706
|
+
* that way and we filled column one and left the rest empty.
|
|
3707
|
+
*/
|
|
3708
|
+
balanceBottomY;
|
|
3709
|
+
balanceHeightPt = 0;
|
|
2223
3710
|
/**
|
|
2224
3711
|
* The first exclusion a line spanning `[yTop-h, yTop]` collides with in the
|
|
2225
3712
|
* current column (x-overlap with the column required).
|
|
@@ -2247,15 +3734,17 @@ var PageAssembler = class {
|
|
|
2247
3734
|
const cl = this.colLeft();
|
|
2248
3735
|
const leftRoom = e.x0 - FLOAT_TEXT_GAP - cl;
|
|
2249
3736
|
const rightRoom = cl + full - (e.x1 + FLOAT_TEXT_GAP);
|
|
2250
|
-
if (rightRoom
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
3737
|
+
if (Math.max(leftRoom, rightRoom) < MIN_WRAP_WIDTH) return {
|
|
3738
|
+
width: full,
|
|
3739
|
+
xOffset: 0,
|
|
3740
|
+
blockedUntilYUp: e.bottomYUp
|
|
3741
|
+
};
|
|
3742
|
+
if (rightRoom >= leftRoom) return {
|
|
3743
|
+
width: rightRoom,
|
|
3744
|
+
xOffset: full - rightRoom
|
|
3745
|
+
};
|
|
2257
3746
|
return {
|
|
2258
|
-
width:
|
|
3747
|
+
width: leftRoom,
|
|
2259
3748
|
xOffset: 0
|
|
2260
3749
|
};
|
|
2261
3750
|
};
|
|
@@ -2274,9 +3763,15 @@ var PageAssembler = class {
|
|
|
2274
3763
|
let y = startY;
|
|
2275
3764
|
for (let i = 0; i < 200; i++) {
|
|
2276
3765
|
const g = this.lineGeometryAt(y, h0);
|
|
3766
|
+
if (g.blockedUntilYUp !== void 0) {
|
|
3767
|
+
widths.push(this.colWidth());
|
|
3768
|
+
y = g.blockedUntilYUp - h0;
|
|
3769
|
+
if (y < this.bottomLimit()) break;
|
|
3770
|
+
continue;
|
|
3771
|
+
}
|
|
2277
3772
|
widths.push(g.width);
|
|
2278
3773
|
if (g.width < this.colWidth()) narrowed = true;
|
|
2279
|
-
else if (
|
|
3774
|
+
else if (narrowed) break;
|
|
2280
3775
|
y -= h0;
|
|
2281
3776
|
if (y < this.bottomLimit()) break;
|
|
2282
3777
|
}
|
|
@@ -2328,7 +3823,7 @@ var PageAssembler = class {
|
|
|
2328
3823
|
const noteNode = this.builder.create("Note", this.builder.root);
|
|
2329
3824
|
structId = this.builder.create("P", noteNode).id;
|
|
2330
3825
|
}
|
|
2331
|
-
out.push(...drawBlocksSequentially(note.blocks, this.ctx.marginLeft, cursor, this.ctx.pageHeight, structId));
|
|
3826
|
+
out.push(...drawBlocksSequentially(note.blocks, this.ctx.marginLeft, cursor, this.ctx.pageHeight, this.ctx.contentWidth, structId));
|
|
2332
3827
|
cursor -= note.heightPt;
|
|
2333
3828
|
}
|
|
2334
3829
|
return out;
|
|
@@ -2353,22 +3848,25 @@ var PageAssembler = class {
|
|
|
2353
3848
|
const footer = pickBand(this.ctx.footerSet, band);
|
|
2354
3849
|
if (header.renderDynamic) this.dynBands.push({
|
|
2355
3850
|
pageIdx: this.pages.length,
|
|
2356
|
-
pageNumber: this.
|
|
3851
|
+
pageNumber: this.pageNumber,
|
|
2357
3852
|
position: "header",
|
|
2358
3853
|
render: header.renderDynamic
|
|
2359
3854
|
});
|
|
2360
3855
|
if (footer.renderDynamic) this.dynBands.push({
|
|
2361
3856
|
pageIdx: this.pages.length,
|
|
2362
|
-
pageNumber: this.
|
|
3857
|
+
pageNumber: this.pageNumber,
|
|
2363
3858
|
position: "footer",
|
|
2364
3859
|
render: footer.renderDynamic
|
|
2365
3860
|
});
|
|
2366
3861
|
this.pages.push({
|
|
2367
3862
|
commands: [
|
|
3863
|
+
...this.pageBackgroundItems(),
|
|
3864
|
+
...this.pageBorderItems(),
|
|
3865
|
+
...this.columnSeparatorItems(),
|
|
2368
3866
|
...header.commands,
|
|
2369
|
-
...this.floatsBehind,
|
|
3867
|
+
...PageAssembler.byZ(this.floatsBehind),
|
|
2370
3868
|
...this.current,
|
|
2371
|
-
...this.floatsFront,
|
|
3869
|
+
...PageAssembler.byZ(this.floatsFront),
|
|
2372
3870
|
...this.renderNotesBand(),
|
|
2373
3871
|
...footer.commands
|
|
2374
3872
|
],
|
|
@@ -2385,35 +3883,212 @@ var PageAssembler = class {
|
|
|
2385
3883
|
this.colStartLen = 0;
|
|
2386
3884
|
this.pageInSection++;
|
|
2387
3885
|
this.globalPageIdx++;
|
|
3886
|
+
this.pageNumber++;
|
|
3887
|
+
if ((this.ctx.properties.lineNumbering?.restart ?? "newPage") === "newPage") this.lineNumber = 0;
|
|
3888
|
+
this.cursorY = this.ctx.pageHeight - this.ctx.marginTop;
|
|
3889
|
+
this.colStartY = this.cursorY;
|
|
3890
|
+
this.bandTopY = this.cursorY;
|
|
3891
|
+
this.balanceBottomY = void 0;
|
|
3892
|
+
this.balanceHeightPt = 0;
|
|
3893
|
+
this.pageStartCtx = this.ctx;
|
|
3894
|
+
};
|
|
3895
|
+
/**
|
|
3896
|
+
* §17.6.4 `w:cols w:sep` — the rule down the middle of every gutter, run the
|
|
3897
|
+
* height of the text area. multi-column-separator-with-line.docx asks for one
|
|
3898
|
+
* and we drew its two columns with nothing between them.
|
|
3899
|
+
*
|
|
3900
|
+
* @returns One border item per gutter, or none when the section asks for no rule.
|
|
3901
|
+
*/
|
|
3902
|
+
columnSeparatorItems = () => {
|
|
3903
|
+
const ctx = this.pageStartCtx;
|
|
3904
|
+
const cols = ctx.columns;
|
|
3905
|
+
if (!cols || cols.length < 2 || ctx.properties.columns?.separator !== true) return [];
|
|
3906
|
+
let deepest = ctx.marginTop;
|
|
3907
|
+
for (const item of this.current) {
|
|
3908
|
+
const bottom = item.type === "line" ? item.baselineY : item.type === "shape" ? 0 : item.y + item.height;
|
|
3909
|
+
if (bottom > deepest) deepest = bottom;
|
|
3910
|
+
}
|
|
3911
|
+
const height = Math.min(deepest, ctx.pageHeight - ctx.marginBottom) - ctx.marginTop;
|
|
3912
|
+
if (height <= 0) return [];
|
|
3913
|
+
const out = [];
|
|
3914
|
+
for (let i = 1; i < cols.length; i++) {
|
|
3915
|
+
const left = cols[i - 1];
|
|
3916
|
+
const right = cols[i];
|
|
3917
|
+
const x = ctx.marginLeft + (left.xOffsetPt + left.widthPt + right.xOffsetPt) / 2;
|
|
3918
|
+
out.push({
|
|
3919
|
+
type: "border",
|
|
3920
|
+
side: "left",
|
|
3921
|
+
x: pt(x),
|
|
3922
|
+
y: pt(ctx.marginTop),
|
|
3923
|
+
width: pt(0),
|
|
3924
|
+
height: pt(height),
|
|
3925
|
+
borderSizePt: COLUMN_SEPARATOR_PT,
|
|
3926
|
+
borderColorHex: "000000"
|
|
3927
|
+
});
|
|
3928
|
+
}
|
|
3929
|
+
return out;
|
|
3930
|
+
};
|
|
3931
|
+
/** The section context the CURRENT page began in (see {@link columnSeparatorItems}). */
|
|
3932
|
+
pageStartCtx;
|
|
3933
|
+
/**
|
|
3934
|
+
* §17.6.10 `w:pgBorders` — the rules around the page. `@w:offsetFrom` says
|
|
3935
|
+
* what each edge's space is measured from: the paper's own edge, or the text
|
|
3936
|
+
* margin the rule stands outside of. a4andborders.docx frames every page and
|
|
3937
|
+
* we drew nothing.
|
|
3938
|
+
*
|
|
3939
|
+
* @returns The border items for this page, in paint order.
|
|
3940
|
+
*/
|
|
3941
|
+
/**
|
|
3942
|
+
* §17.2.1 — what the page is painted before anything is drawn on it: the
|
|
3943
|
+
* `v:background`'s PICTURE or GRADIENT when it has one, else the flat
|
|
3944
|
+
* `w:background @w:color`. Read as the colour alone, fill.docx's five-stop
|
|
3945
|
+
* sweep came out the navy of its fallback and tdf126533_pageBitmap.docx's
|
|
3946
|
+
* paper came out plain.
|
|
3947
|
+
*
|
|
3948
|
+
* @returns The background items for this page (at most one).
|
|
3949
|
+
*/
|
|
3950
|
+
pageBackgroundItems = () => {
|
|
3951
|
+
const options = this.ctx.options;
|
|
3952
|
+
const w = this.ctx.pageWidth;
|
|
3953
|
+
const h = this.ctx.pageHeight;
|
|
3954
|
+
const fill = options?.pageBackgroundFill;
|
|
3955
|
+
if (fill?.kind === "picture" && this.backgroundImageName !== void 0) return [{
|
|
3956
|
+
type: "image",
|
|
3957
|
+
x: pt(0),
|
|
3958
|
+
y: pt(0),
|
|
3959
|
+
width: pt(w),
|
|
3960
|
+
height: pt(h),
|
|
3961
|
+
imageResourceName: this.backgroundImageName
|
|
3962
|
+
}];
|
|
3963
|
+
if (fill?.kind === "gradient" && fill.gradient) return [{
|
|
3964
|
+
type: "shape",
|
|
3965
|
+
shape: {
|
|
3966
|
+
paths: [rectAtPath(0, 0, w, h)],
|
|
3967
|
+
fillColorHex: gradientToSolid(fill.gradient),
|
|
3968
|
+
fillGradient: fill.gradient,
|
|
3969
|
+
transform: flipTransform([
|
|
3970
|
+
1,
|
|
3971
|
+
0,
|
|
3972
|
+
0,
|
|
3973
|
+
1,
|
|
3974
|
+
0,
|
|
3975
|
+
0
|
|
3976
|
+
], h)
|
|
3977
|
+
}
|
|
3978
|
+
}];
|
|
3979
|
+
if (options?.pageBackgroundColorHex !== void 0) return [{
|
|
3980
|
+
type: "fill",
|
|
3981
|
+
x: pt(0),
|
|
3982
|
+
y: pt(0),
|
|
3983
|
+
width: pt(w),
|
|
3984
|
+
height: pt(h),
|
|
3985
|
+
fillColorHex: options.pageBackgroundColorHex
|
|
3986
|
+
}];
|
|
3987
|
+
return [];
|
|
3988
|
+
};
|
|
3989
|
+
pageBorderItems = () => {
|
|
3990
|
+
const pg = this.ctx.properties.pageBorders;
|
|
3991
|
+
if (!pg) return [];
|
|
3992
|
+
const H = this.ctx.pageHeight;
|
|
3993
|
+
const W = this.ctx.pageWidth;
|
|
3994
|
+
const out = [];
|
|
3995
|
+
for (const side of [
|
|
3996
|
+
"top",
|
|
3997
|
+
"bottom",
|
|
3998
|
+
"left",
|
|
3999
|
+
"right"
|
|
4000
|
+
]) {
|
|
4001
|
+
const edge = pg.borders[side];
|
|
4002
|
+
if (!edge || edge.style === "none") continue;
|
|
4003
|
+
const gap = edge.spacePt ?? 0;
|
|
4004
|
+
const x0 = pg.offsetFrom === "page" ? gap : this.ctx.marginLeft - gap;
|
|
4005
|
+
const x1 = pg.offsetFrom === "page" ? W - gap : W - this.ctx.marginLeft + gap;
|
|
4006
|
+
const yTop = pg.offsetFrom === "page" ? gap : this.ctx.marginTop - gap;
|
|
4007
|
+
const yBottom = pg.offsetFrom === "page" ? H - gap : H - this.ctx.marginBottom + gap;
|
|
4008
|
+
out.push({
|
|
4009
|
+
type: "border",
|
|
4010
|
+
side,
|
|
4011
|
+
x: pt(Math.max(0, x0)),
|
|
4012
|
+
y: pt(Math.max(0, yTop)),
|
|
4013
|
+
width: pt(Math.max(0, x1 - x0)),
|
|
4014
|
+
height: pt(Math.max(0, yBottom - yTop)),
|
|
4015
|
+
borderSizePt: edge.width ?? DEFAULT_BORDER_SIZE_EIGHTH * EIGHTH_PT,
|
|
4016
|
+
borderColorHex: edge.colorHex ?? "000000",
|
|
4017
|
+
...edge.style !== "single" ? { borderStyle: edge.style } : {}
|
|
4018
|
+
});
|
|
4019
|
+
}
|
|
4020
|
+
return out;
|
|
4021
|
+
};
|
|
4022
|
+
/**
|
|
4023
|
+
* Throw the in-progress page away and reset for the next one.
|
|
4024
|
+
*
|
|
4025
|
+
* A section boundary reached with a page that draws nothing is the one place
|
|
4026
|
+
* this is wanted: {@link flushPage} keeps such a page whenever the cursor
|
|
4027
|
+
* moved at all, which is right in the middle of a flow and wrong at a
|
|
4028
|
+
* boundary, where the space consumed was the section break itself.
|
|
4029
|
+
*/
|
|
4030
|
+
dropPage = () => {
|
|
4031
|
+
this.current = [];
|
|
4032
|
+
this.floatsBehind = [];
|
|
4033
|
+
this.floatsFront = [];
|
|
4034
|
+
this.exclusions = [];
|
|
4035
|
+
this.pageNotes = [];
|
|
4036
|
+
this.noteReserve = 0;
|
|
4037
|
+
this.colIdx = 0;
|
|
4038
|
+
this.colStartLen = 0;
|
|
2388
4039
|
this.cursorY = this.ctx.pageHeight - this.ctx.marginTop;
|
|
2389
4040
|
this.colStartY = this.cursorY;
|
|
4041
|
+
this.bandTopY = this.cursorY;
|
|
4042
|
+
this.balanceBottomY = void 0;
|
|
4043
|
+
this.balanceHeightPt = 0;
|
|
2390
4044
|
};
|
|
2391
4045
|
};
|
|
2392
|
-
function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph) {
|
|
4046
|
+
function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", notes, bookmarkPositions, reflowParagraph, backgroundImageName) {
|
|
2393
4047
|
if (sectionCtxs.length === 0) return [];
|
|
2394
|
-
const asm = new PageAssembler(sectionCtxs, builder, notes, bookmarkPositions);
|
|
4048
|
+
const asm = new PageAssembler(sectionCtxs, builder, notes, bookmarkPositions, backgroundImageName);
|
|
4049
|
+
const balanceIfEndsContinuous = (fromBlock) => {
|
|
4050
|
+
const here = asm.ctx;
|
|
4051
|
+
const next = sectionCtxs[asm.secIdx + 1];
|
|
4052
|
+
if ((here.columns?.length ?? 1) < 2) return;
|
|
4053
|
+
if (!next?.continuous) return;
|
|
4054
|
+
if (next.pageWidth !== here.pageWidth || next.pageHeight !== here.pageHeight) return;
|
|
4055
|
+
const end = Math.min(here.endIndex, blocks.length);
|
|
4056
|
+
asm.beginBalancedBand(blocksHeight(blocks.slice(fromBlock, end)));
|
|
4057
|
+
};
|
|
4058
|
+
balanceIfEndsContinuous(0);
|
|
2395
4059
|
for (let blockIdx = 0; blockIdx < blocks.length; blockIdx++) {
|
|
2396
4060
|
while (asm.secIdx < sectionCtxs.length - 1 && blockIdx >= asm.ctx.endIndex) {
|
|
2397
|
-
asm.
|
|
4061
|
+
const next = sectionCtxs[asm.secIdx + 1];
|
|
4062
|
+
if (next.continuous && next.pageWidth === asm.ctx.pageWidth && next.pageHeight === asm.ctx.pageHeight) {
|
|
4063
|
+
const end = Math.min(next.endIndex, blocks.length);
|
|
4064
|
+
asm.startBandSection(next, blocksHeight(blocks.slice(blockIdx, end)));
|
|
4065
|
+
continue;
|
|
4066
|
+
}
|
|
4067
|
+
if (asm.pageHasContent() || asm.pageInSection === 0) asm.flushPage(true);
|
|
4068
|
+
else asm.dropPage();
|
|
2398
4069
|
asm.secIdx++;
|
|
2399
4070
|
asm.ctx = sectionCtxs[asm.secIdx];
|
|
4071
|
+
asm.restartPageNumbers(asm.ctx);
|
|
2400
4072
|
asm.pageInSection = 0;
|
|
2401
4073
|
asm.cursorY = asm.ctx.pageHeight - asm.ctx.marginTop;
|
|
4074
|
+
balanceIfEndsContinuous(blockIdx);
|
|
2402
4075
|
}
|
|
2403
4076
|
const block = blocks[blockIdx];
|
|
2404
4077
|
if (asm.pendingPageBreak) {
|
|
2405
4078
|
asm.pendingPageBreak = false;
|
|
2406
|
-
|
|
4079
|
+
asm.flushPage(true);
|
|
2407
4080
|
}
|
|
2408
4081
|
if (builder && !(block.kind === "paragraph" && block.list)) asm.listStack.length = 0;
|
|
2409
4082
|
if (block.kind === "paragraph") {
|
|
2410
|
-
if (block.resolved.pageBreakBefore && asm.pageHasContent()) asm.flushPage();
|
|
4083
|
+
if ((block.resolved.pageBreakBefore || block.pageBreakBefore) && asm.pageHasContent()) asm.flushPage();
|
|
2411
4084
|
asm.cursorY -= block.spacingBeforePt;
|
|
2412
4085
|
let pb = block;
|
|
2413
4086
|
if (reflowParagraph && pb.source && asm.exclusions.length > 0) {
|
|
2414
4087
|
const widths = asm.lineWidthsFor(block, asm.cursorY);
|
|
2415
4088
|
if (widths) pb = reflowParagraph(pb.source, asm.colWidth(), widths);
|
|
2416
4089
|
}
|
|
4090
|
+
const borderTopY = asm.cursorY;
|
|
4091
|
+
const borderPage = asm.pages.length;
|
|
2417
4092
|
let structId;
|
|
2418
4093
|
let markerLblId;
|
|
2419
4094
|
const firstLine = pb.lines[0];
|
|
@@ -2428,7 +4103,8 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2428
4103
|
if (lang && lang !== defaultLang) builder.node(structId).lang = lang;
|
|
2429
4104
|
}
|
|
2430
4105
|
let firstLineOfBlock = true;
|
|
2431
|
-
for (const line of pb.lines) {
|
|
4106
|
+
for (const [lineIdx, line] of pb.lines.entries()) {
|
|
4107
|
+
if (pb.columnBreakLines?.has(lineIdx) && asm.colHasContent()) asm.advanceColumn();
|
|
2432
4108
|
const h = computeLineHeight(line, pb.resolved);
|
|
2433
4109
|
let newNotes = asm.lineFootnotes(line);
|
|
2434
4110
|
const addedReserve = (sub) => sub.reduce((sum, x) => sum + x.heightPt, 0) + (asm.pageNotes.length === 0 && sub.length > 0 ? FOOTNOTE_SEPARATOR_HEIGHT : 0);
|
|
@@ -2456,10 +4132,15 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2456
4132
|
});
|
|
2457
4133
|
}
|
|
2458
4134
|
}
|
|
4135
|
+
for (let guard = 0; guard < 8 && asm.exclusions.length > 0; guard++) {
|
|
4136
|
+
const blocked = asm.lineGeometryAt(asm.cursorY, h).blockedUntilYUp;
|
|
4137
|
+
if (blocked === void 0 || blocked >= asm.cursorY) break;
|
|
4138
|
+
asm.cursorY = blocked;
|
|
4139
|
+
}
|
|
2459
4140
|
asm.cursorY -= h;
|
|
2460
4141
|
const indentLeft = pb.resolved.indentLeft + (line.firstLine ? pb.resolved.indentFirstLine : 0);
|
|
2461
4142
|
const offset = alignmentOffset(pb.resolved.alignment, line.contentWidthPt, line.availableWidthPt);
|
|
2462
|
-
const baselineY = pt(asm.ctx.pageHeight - (asm.cursorY +
|
|
4143
|
+
const baselineY = pt(asm.ctx.pageHeight - (asm.cursorY + lineBaselineOffset(line, pb.resolved)));
|
|
2463
4144
|
const exclusionShift = asm.exclusions.length > 0 ? asm.lineGeometryAt(asm.cursorY + h, h).xOffset : 0;
|
|
2464
4145
|
const originX = asm.colLeft() + indentLeft + offset + exclusionShift;
|
|
2465
4146
|
if (markerLblId !== void 0 && line === firstLine) {
|
|
@@ -2499,87 +4180,53 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2499
4180
|
baselineY,
|
|
2500
4181
|
...structId !== void 0 ? { structId } : {}
|
|
2501
4182
|
});
|
|
4183
|
+
asm.emitLineNumber(baselineY, line);
|
|
2502
4184
|
}
|
|
4185
|
+
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
4186
|
asm.cursorY -= pb.spacingAfterPt;
|
|
2504
4187
|
if (pb.pageBreakAfter) asm.pendingPageBreak = true;
|
|
2505
4188
|
} else if (block.kind === "image") {
|
|
2506
4189
|
const figId = builder ? createFigure(builder, block.altText, "Image") : void 0;
|
|
2507
4190
|
const emitImageAt = (x, topYUp, sink) => {
|
|
4191
|
+
const ins = block.drawInset;
|
|
2508
4192
|
sink.push({
|
|
2509
4193
|
type: "image",
|
|
2510
|
-
x: pt(x),
|
|
2511
|
-
y: pt(asm.ctx.pageHeight - topYUp),
|
|
2512
|
-
width: pt(block.widthPt),
|
|
2513
|
-
height: pt(block.heightPt),
|
|
4194
|
+
x: pt(x + (ins?.dxPt ?? 0)),
|
|
4195
|
+
y: pt(asm.ctx.pageHeight - (topYUp - (ins?.dyTopPt ?? 0))),
|
|
4196
|
+
width: pt(ins?.widthPt ?? block.widthPt),
|
|
4197
|
+
height: pt(ins?.heightPt ?? block.heightPt),
|
|
2514
4198
|
imageResourceName: block.resourceName,
|
|
4199
|
+
...block.crop ? { crop: block.crop } : {},
|
|
4200
|
+
...block.rotationDeg ? { rotationDeg: block.rotationDeg } : {},
|
|
4201
|
+
...block.flipH ? { flipH: true } : {},
|
|
4202
|
+
...block.flipV ? { flipV: true } : {},
|
|
4203
|
+
...block.flipH ? { flipH: true } : {},
|
|
4204
|
+
...block.flipV ? { flipV: true } : {},
|
|
2515
4205
|
...figId !== void 0 ? { structId: figId } : {}
|
|
2516
4206
|
});
|
|
4207
|
+
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
4208
|
};
|
|
2518
4209
|
if (isOutOfFlowFloat(block.float)) {
|
|
2519
4210
|
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
|
-
});
|
|
4211
|
+
const fy = asm.floatTopYUp(block.float, block.heightPt);
|
|
4212
|
+
emitImageAt(fx, fy, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
|
|
4213
|
+
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
|
|
2528
4214
|
} else {
|
|
2529
4215
|
asm.cursorY -= block.spacingBeforePt;
|
|
2530
4216
|
if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
|
|
2531
4217
|
asm.cursorY -= block.heightPt;
|
|
2532
|
-
|
|
4218
|
+
const offset = alignmentOffset(block.resolvedAlignment, block.widthPt, asm.colWidth());
|
|
4219
|
+
emitImageAt(asm.colLeft() + offset, asm.cursorY + block.heightPt, asm.current);
|
|
2533
4220
|
asm.cursorY -= block.spacingAfterPt;
|
|
2534
4221
|
}
|
|
2535
4222
|
} else if (block.kind === "shape") {
|
|
2536
4223
|
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
|
-
};
|
|
4224
|
+
const emitShapeAt = (x, bottomYUp, sink) => emitShapeItems(block, x, bottomYUp, sink, asm.ctx.pageHeight, figId);
|
|
2573
4225
|
if (isOutOfFlowFloat(block.float)) {
|
|
2574
4226
|
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
|
-
});
|
|
4227
|
+
const fy = asm.floatTopYUp(block.float, block.heightPt);
|
|
4228
|
+
emitShapeAt(fx, fy - block.heightPt, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
|
|
4229
|
+
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
|
|
2583
4230
|
} else {
|
|
2584
4231
|
asm.cursorY -= block.spacingBeforePt;
|
|
2585
4232
|
if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
|
|
@@ -2590,44 +4237,14 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2590
4237
|
}
|
|
2591
4238
|
} else if (block.kind === "chart") {
|
|
2592
4239
|
const figId = builder ? createFigure(builder, block.altText, "Chart") : void 0;
|
|
2593
|
-
const fig = figId !== void 0 ? { structId: figId } : {};
|
|
2594
4240
|
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
|
-
});
|
|
4241
|
+
sink.push(...chartPageItems(block, x, bottomYUp, asm.ctx.pageHeight, figId));
|
|
2620
4242
|
};
|
|
2621
4243
|
if (isOutOfFlowFloat(block.float)) {
|
|
2622
4244
|
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
|
-
});
|
|
4245
|
+
const fy = asm.floatTopYUp(block.float, block.heightPt);
|
|
4246
|
+
emitChartAt(fx, fy - block.heightPt, PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder));
|
|
4247
|
+
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.widthPt, block.heightPt);
|
|
2631
4248
|
} else {
|
|
2632
4249
|
asm.cursorY -= block.spacingBeforePt;
|
|
2633
4250
|
if (asm.cursorY - block.heightPt < asm.bottomLimit() && asm.colHasContent()) asm.advanceColumn();
|
|
@@ -2638,6 +4255,19 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2638
4255
|
}
|
|
2639
4256
|
} else {
|
|
2640
4257
|
const colCount = block.rows.reduce((max, r) => Math.max(max, r.cells.reduce((s, c) => Math.max(s, c.colStart + c.colSpan), 0)), 0);
|
|
4258
|
+
const anchorY = isOutOfFlowFloat(block.float) ? asm.floatTopYUp(block.float, block.heightPt) : 0;
|
|
4259
|
+
if (isOutOfFlowFloat(block.float) && anchorY - block.heightPt >= asm.ctx.marginBottom) {
|
|
4260
|
+
const fx = asm.floatX(block.float, block.totalWidthPt);
|
|
4261
|
+
const fy = anchorY;
|
|
4262
|
+
const sink = PageAssembler.zSink(block.float.behind ? asm.floatsBehind : asm.floatsFront, block.float.zOrder);
|
|
4263
|
+
let rowY = fy;
|
|
4264
|
+
for (const row of block.rows) {
|
|
4265
|
+
emitRowChunk(sink, row, fx, rowY, asm.ctx.pageHeight, colCount);
|
|
4266
|
+
rowY -= row.heightPt;
|
|
4267
|
+
}
|
|
4268
|
+
if (block.float.wrap !== "none") asm.excludeFloat(block.float, fx, fy, block.totalWidthPt, block.heightPt);
|
|
4269
|
+
continue;
|
|
4270
|
+
}
|
|
2641
4271
|
const headerRows = [];
|
|
2642
4272
|
for (const r of block.rows) if (r.isHeader) headerRows.push(r);
|
|
2643
4273
|
else break;
|
|
@@ -2917,7 +4547,26 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
|
|
|
2917
4547
|
const slack = cell.verticalAlign !== void 0 ? boxHeightPt - cell.padTopPt - cell.contentHeightPt - cell.padBottomPt : 0;
|
|
2918
4548
|
const vOffset = slack <= 0 ? 0 : cell.verticalAlign === "bottom" ? slack : cell.verticalAlign === "center" ? slack / 2 : 0;
|
|
2919
4549
|
let textY = rowTop - cell.padTopPt - vOffset;
|
|
2920
|
-
|
|
4550
|
+
const nestedX = cellX + cell.padLeftPt;
|
|
4551
|
+
const drawNestedBefore = (n) => {
|
|
4552
|
+
for (const nt of cell.nestedTables ?? []) {
|
|
4553
|
+
if (nt.afterLine !== n) continue;
|
|
4554
|
+
for (const nrow of nt.block.rows) {
|
|
4555
|
+
const nestedIds = structId !== void 0 ? nrow.cells.map(() => structId) : void 0;
|
|
4556
|
+
emitRowChunk(out, nrow, nestedX, textY, pageHeight, nt.block.colCount, nestedIds);
|
|
4557
|
+
textY -= nrow.heightPt;
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
for (const cs of cell.shapes ?? []) {
|
|
4561
|
+
if (cs.afterLine !== n) continue;
|
|
4562
|
+
const offset = alignmentOffset(cs.block.resolvedAlignment, cs.block.widthPt, cell.widthPt - cell.padLeftPt - cell.padRightPt);
|
|
4563
|
+
textY -= cs.block.heightPt;
|
|
4564
|
+
emitShapeItems(cs.block, nestedX + offset, textY, out, pageHeight, structId);
|
|
4565
|
+
}
|
|
4566
|
+
};
|
|
4567
|
+
for (const [lineIdx, line] of cell.lines.entries()) {
|
|
4568
|
+
drawNestedBefore(lineIdx);
|
|
4569
|
+
textY -= cell.lineGaps?.get(lineIdx) ?? 0;
|
|
2921
4570
|
const h = computeLineHeight(line, line.resolved);
|
|
2922
4571
|
textY -= h;
|
|
2923
4572
|
const offset = alignmentOffset(line.resolved.alignment, line.contentWidthPt, cell.widthPt - cell.padLeftPt - cell.padRightPt);
|
|
@@ -2926,7 +4575,7 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
|
|
|
2926
4575
|
type: "line",
|
|
2927
4576
|
line,
|
|
2928
4577
|
originX: pt(cellX + cell.padLeftPt + indentLeft + offset),
|
|
2929
|
-
baselineY: pt(pageHeight - (textY +
|
|
4578
|
+
baselineY: pt(pageHeight - (textY + lineBaselineOffset(line, line.resolved))),
|
|
2930
4579
|
...cell.clipped ? { clip: {
|
|
2931
4580
|
x: pt(cellX),
|
|
2932
4581
|
y: pt(pageHeight - rowTop),
|
|
@@ -2936,14 +4585,7 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
|
|
|
2936
4585
|
...structId !== void 0 ? { structId } : {}
|
|
2937
4586
|
});
|
|
2938
4587
|
}
|
|
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
|
-
}
|
|
4588
|
+
drawNestedBefore(cell.lines.length);
|
|
2947
4589
|
}
|
|
2948
4590
|
}
|
|
2949
4591
|
function splitRowIntoChunks(row, capacity) {
|
|
@@ -3147,4 +4789,4 @@ function emitRowBottomEdge(out, row, marginLeft, rowBottom, pageHeight) {
|
|
|
3147
4789
|
}
|
|
3148
4790
|
}
|
|
3149
4791
|
//#endregion
|
|
3150
|
-
export { layoutStyledDocument };
|
|
4792
|
+
export { GLUE_SHRINK_RATIO, layoutStyledDocument };
|