reamkit 1.15.3 → 1.16.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/converter/facade.d.ts +5 -0
- package/dist/esm/core/converter/ream.d.ts +6 -0
- package/dist/esm/core/converter/ream.js +34 -8
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +187 -1
- package/dist/esm/core/drawingml/chart-geometry.d.ts +28 -0
- package/dist/esm/core/drawingml/chart-geometry.js +371 -88
- package/dist/esm/core/drawingml/chart-parser.js +262 -11
- package/dist/esm/core/drawingml/colors.d.ts +6 -6
- package/dist/esm/core/drawingml/colors.js +58 -18
- package/dist/esm/core/drawingml/preset-geometry.js +7 -6
- package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
- package/dist/esm/core/drawingml/theme-parser.d.ts +41 -0
- package/dist/esm/core/drawingml/theme-parser.js +74 -1
- package/dist/esm/core/font/ttf-subset.js +26 -1
- package/dist/esm/core/images.js +172 -43
- package/dist/esm/core/indexed-colors.d.ts +11 -0
- package/dist/esm/core/indexed-colors.js +81 -0
- package/dist/esm/core/ir/sheet.d.ts +67 -3
- package/dist/esm/{excel → core}/number-format.d.ts +37 -0
- package/dist/esm/core/number-format.js +800 -0
- package/dist/esm/core/opc/alternate-content.d.ts +13 -0
- package/dist/esm/core/opc/alternate-content.js +72 -0
- package/dist/esm/core/opc/core-properties.js +1 -0
- package/dist/esm/core/opc/package.d.ts +14 -1
- package/dist/esm/core/opc/package.js +43 -4
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/opc/xml-entities.d.ts +12 -0
- package/dist/esm/core/opc/xml-entities.js +91 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
- package/dist/esm/core/vector.d.ts +13 -0
- package/dist/esm/excel/activex-parser.d.ts +9 -0
- package/dist/esm/excel/activex-parser.js +16 -1
- package/dist/esm/excel/column-bands.d.ts +26 -1
- package/dist/esm/excel/column-bands.js +156 -20
- package/dist/esm/excel/comments-parser.js +5 -3
- package/dist/esm/excel/conditional-format.d.ts +25 -2
- package/dist/esm/excel/conditional-format.js +63 -19
- package/dist/esm/excel/escaped-text.d.ts +9 -0
- package/dist/esm/excel/escaped-text.js +20 -0
- package/dist/esm/excel/form-control-parser.js +1 -0
- package/dist/esm/excel/formula/dates.js +1 -1
- package/dist/esm/excel/header-footer.d.ts +9 -1
- package/dist/esm/excel/header-footer.js +162 -14
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/index.js +2 -2
- package/dist/esm/excel/pivot-table-parser.js +1 -0
- package/dist/esm/excel/print-model.d.ts +75 -0
- package/dist/esm/excel/print-model.js +902 -77
- package/dist/esm/excel/printer-settings.d.ts +16 -0
- package/dist/esm/excel/printer-settings.js +36 -0
- package/dist/esm/excel/rich-value.d.ts +13 -0
- package/dist/esm/excel/rich-value.js +120 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -7
- package/dist/esm/excel/sheet-drawing.d.ts +11 -0
- package/dist/esm/excel/sheet-drawing.js +26 -3
- package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
- package/dist/esm/excel/sheet-shape-parser.js +461 -32
- package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
- package/dist/esm/excel/sheet-to-flow.js +551 -36
- package/dist/esm/excel/slicer-parser.js +1 -0
- package/dist/esm/excel/styles-parser.d.ts +30 -2
- package/dist/esm/excel/styles-parser.js +177 -40
- package/dist/esm/excel/table-parser.js +1 -0
- package/dist/esm/excel/vml-drawing.d.ts +68 -0
- package/dist/esm/excel/vml-drawing.js +158 -0
- package/dist/esm/excel/workbook-parser.d.ts +5 -0
- package/dist/esm/excel/workbook-parser.js +5 -1
- package/dist/esm/excel/worksheet-parser.d.ts +2 -1
- package/dist/esm/excel/worksheet-parser.js +149 -28
- package/dist/esm/excel/xlsx-reader.js +271 -33
- package/dist/esm/excel/xlsx-to-pdf.js +12 -1
- package/dist/esm/html/html-writer.js +11 -2
- package/dist/esm/layout/page-doc.d.ts +26 -0
- package/dist/esm/layout/styled-layout.d.ts +2 -0
- package/dist/esm/layout/styled-layout.js +493 -64
- package/dist/esm/pdf/cid-font.js +20 -9
- package/dist/esm/pdf/objects.d.ts +13 -0
- package/dist/esm/pdf/objects.js +18 -1
- package/dist/esm/pdf/styled-page-emitter.js +164 -32
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf/vector-graphics.d.ts +3 -1
- package/dist/esm/pdf/vector-graphics.js +19 -2
- package/dist/esm/pptx/pptx-reader.js +1 -0
- package/dist/esm/pptx/slide-parser.js +12 -1
- package/dist/esm/svg/svg-writer.js +23 -4
- package/dist/esm/word/document-parser.js +1 -0
- package/dist/esm/word/drawing-parser.d.ts +34 -1
- package/dist/esm/word/drawing-parser.js +52 -2
- package/dist/esm/word/numbering-parser.js +1 -0
- package/dist/esm/word/settings-parser.js +1 -0
- package/dist/esm/word/styles-parser.js +1 -0
- package/package.json +7 -3
- package/dist/esm/excel/number-format.js +0 -476
|
@@ -17,7 +17,7 @@ import "../core/style-cascade/index.js";
|
|
|
17
17
|
import { PathBuilder, flipTransform } from "../core/vector.js";
|
|
18
18
|
import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
|
|
19
19
|
import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
|
|
20
|
-
import { buildChartScene } from "../core/drawingml/chart-geometry.js";
|
|
20
|
+
import { buildChartScene, legendSeriesName } from "../core/drawingml/chart-geometry.js";
|
|
21
21
|
import { layoutMath, mathGlyphSegments, variantStyle } from "./math-layout.js";
|
|
22
22
|
import { rectPath } from "../core/drawingml/preset-geometry.js";
|
|
23
23
|
import { buildShapePaths, buildShapeTransform, buildStroke, gradientToSolid } from "../core/drawingml/shape-render.js";
|
|
@@ -400,16 +400,18 @@ function buildSectionContext(section, options, headersFooters, fontResources) {
|
|
|
400
400
|
const headerSet = layoutHeaderSet(section.properties, headersFooters, options, fontResources, contentWidth, dims.marginLeft, dims.pageHeight, dims.headerOffsetPt);
|
|
401
401
|
const footerSet = layoutFooterSet(section.properties, headersFooters, options, fontResources, contentWidth, dims.marginLeft, dims.pageHeight, dims.footerOffsetPt);
|
|
402
402
|
const columns = buildColumnGeometry(section.properties.columns, contentWidth);
|
|
403
|
+
const headerBottom = dims.headerOffsetPt + Math.max(headerSet.default.heightPt ?? 0, headerSet.first.heightPt ?? 0, headerSet.even.heightPt ?? 0);
|
|
404
|
+
const marginTop = Math.max(dims.marginTop, headerBottom);
|
|
403
405
|
return {
|
|
404
406
|
endIndex: section.endIndex,
|
|
405
407
|
properties: section.properties,
|
|
406
408
|
pageWidth: dims.pageWidth,
|
|
407
409
|
pageHeight: dims.pageHeight,
|
|
408
410
|
marginLeft: dims.marginLeft,
|
|
409
|
-
marginTop
|
|
411
|
+
marginTop,
|
|
410
412
|
marginBottom: dims.marginBottom,
|
|
411
413
|
contentWidth,
|
|
412
|
-
pageContentHeight: dims.pageHeight -
|
|
414
|
+
pageContentHeight: dims.pageHeight - marginTop - dims.marginBottom,
|
|
413
415
|
...columns ? { columns } : {},
|
|
414
416
|
headerSet,
|
|
415
417
|
footerSet,
|
|
@@ -455,14 +457,24 @@ function layoutHeaderSet(section, headersFooters, options, fontResources, conten
|
|
|
455
457
|
if (!ref) return { commands: [] };
|
|
456
458
|
const content = headersFooters.get(ref.relationshipId);
|
|
457
459
|
if (!content) return { commands: [] };
|
|
460
|
+
let measured = 0;
|
|
458
461
|
const render = (c) => {
|
|
459
|
-
|
|
462
|
+
const blocks = laidOutBlocksFor(c, options, fontResources, contentWidth);
|
|
463
|
+
measured = blocksHeight(blocks);
|
|
464
|
+
return markPagination(drawBlocksSequentially(blocks, marginLeft, pageHeight - headerOffsetPt, pageHeight));
|
|
460
465
|
};
|
|
461
|
-
if (contentHasPageFields(content))
|
|
462
|
-
|
|
463
|
-
|
|
466
|
+
if (contentHasPageFields(content)) {
|
|
467
|
+
render(substitutePageFields(content, 1, 1));
|
|
468
|
+
return {
|
|
469
|
+
commands: [],
|
|
470
|
+
heightPt: measured,
|
|
471
|
+
renderDynamic: (n, total) => render(substitutePageFields(content, n, total))
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
commands: render(content),
|
|
476
|
+
heightPt: measured
|
|
464
477
|
};
|
|
465
|
-
return { commands: render(content) };
|
|
466
478
|
};
|
|
467
479
|
return {
|
|
468
480
|
default: band("default"),
|
|
@@ -470,6 +482,10 @@ function layoutHeaderSet(section, headersFooters, options, fontResources, conten
|
|
|
470
482
|
even: band("even")
|
|
471
483
|
};
|
|
472
484
|
}
|
|
485
|
+
/** Total laid-out height of a run of blocks, paragraph spacing included. */
|
|
486
|
+
function blocksHeight(blocks) {
|
|
487
|
+
return blocks.reduce((sum, b) => sum + (b.kind === "paragraph" ? b.spacingBeforePt + b.heightPt + b.spacingAfterPt : b.heightPt), 0);
|
|
488
|
+
}
|
|
473
489
|
function layoutFooterSet(section, headersFooters, options, fontResources, contentWidth, marginLeft, pageHeight, footerOffsetPt) {
|
|
474
490
|
const band = (type) => {
|
|
475
491
|
const ref = refByType(section.footers, type);
|
|
@@ -586,7 +602,16 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
586
602
|
for (const el of text.content) {
|
|
587
603
|
if (el.kind !== "paragraph") continue;
|
|
588
604
|
const blk = layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, innerWidth);
|
|
589
|
-
|
|
605
|
+
const lines = blk.lines.length > 0 ? blk.lines : [{
|
|
606
|
+
tokens: [],
|
|
607
|
+
contentWidthPt: 0,
|
|
608
|
+
maxFontSizePt: el.paragraph.runs[0]?.properties.fontSizePt ?? options.styles.defaultRunProperties.fontSizePt ?? pt(11),
|
|
609
|
+
availableWidthPt: innerWidth,
|
|
610
|
+
firstLine: true,
|
|
611
|
+
resolved: blk.resolved,
|
|
612
|
+
isLastInParagraph: true
|
|
613
|
+
}];
|
|
614
|
+
for (const line of lines) {
|
|
590
615
|
textLines.push(line);
|
|
591
616
|
textHeightPt += computeLineHeight(line, blk.resolved);
|
|
592
617
|
}
|
|
@@ -601,6 +626,7 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
601
626
|
...fillColorHex ? { fillColorHex } : {},
|
|
602
627
|
...fillGradient ? { fillGradient } : {},
|
|
603
628
|
...stroke ? { stroke } : {},
|
|
629
|
+
...shape.shadow ? { shadow: shape.shadow } : {},
|
|
604
630
|
rotation60k: t?.rotation60k ?? 0,
|
|
605
631
|
flipH: t?.flipH ?? false,
|
|
606
632
|
flipV: t?.flipV ?? false,
|
|
@@ -666,6 +692,8 @@ function buildChartLayout(chart, widthPt, heightPt, font) {
|
|
|
666
692
|
texts: []
|
|
667
693
|
};
|
|
668
694
|
const shapes = [];
|
|
695
|
+
if (scene.background) shapes.push(rectPrim(scene.background));
|
|
696
|
+
for (const g of scene.gridlines ?? []) shapes.push(polylinePrim(g));
|
|
669
697
|
for (const pg of scene.polygons ?? []) shapes.push(polygonPrim(pg));
|
|
670
698
|
for (const r of scene.rects) shapes.push(rectPrim(r));
|
|
671
699
|
for (const p of scene.polylines) shapes.push(polylinePrim(p));
|
|
@@ -682,7 +710,6 @@ var CF_ICON_SIZE_PT = 9;
|
|
|
682
710
|
var CF_ICON_GUTTER_PT = 12;
|
|
683
711
|
var CF_ICON_EMPTY_HEX = "BFBFBF";
|
|
684
712
|
var DROPDOWN_BUTTON_PT = 11;
|
|
685
|
-
var DROPDOWN_GUTTER_PT = 13;
|
|
686
713
|
var DROPDOWN_FILL_HEX = "F0F0F0";
|
|
687
714
|
var DROPDOWN_BORDER_HEX = "B0B0B0";
|
|
688
715
|
var DROPDOWN_ARROW_HEX = "595959";
|
|
@@ -839,9 +866,16 @@ function wedgePrim(w) {
|
|
|
839
866
|
function labelPrim(l, font) {
|
|
840
867
|
const line = makeChartLabelLine(l.text, font, l.sizePt, l.colorHex);
|
|
841
868
|
const w = line.contentWidthPt;
|
|
869
|
+
const shift = l.align === "center" ? -w / 2 : l.align === "right" ? -w : 0;
|
|
870
|
+
if (l.rotationDeg) return {
|
|
871
|
+
line,
|
|
872
|
+
x: l.x,
|
|
873
|
+
y: l.y + shift,
|
|
874
|
+
rotationDeg: l.rotationDeg
|
|
875
|
+
};
|
|
842
876
|
return {
|
|
843
877
|
line,
|
|
844
|
-
x: l.
|
|
878
|
+
x: l.x + shift,
|
|
845
879
|
y: l.y
|
|
846
880
|
};
|
|
847
881
|
}
|
|
@@ -1020,7 +1054,16 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1020
1054
|
}
|
|
1021
1055
|
addRun(run, el.paragraph);
|
|
1022
1056
|
}
|
|
1023
|
-
else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells)
|
|
1057
|
+
else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) {
|
|
1058
|
+
if (cell.properties.hashOnOverflow === true) for (const inner of cell.content) {
|
|
1059
|
+
if (inner.kind !== "paragraph") continue;
|
|
1060
|
+
for (const run of inner.paragraph.runs) addRun({
|
|
1061
|
+
text: "#",
|
|
1062
|
+
properties: run.properties
|
|
1063
|
+
}, inner.paragraph);
|
|
1064
|
+
}
|
|
1065
|
+
visit(cell.content);
|
|
1066
|
+
}
|
|
1024
1067
|
else if (el.kind === "shape") {
|
|
1025
1068
|
if (el.shape.text) visit(el.shape.text.content);
|
|
1026
1069
|
} else if (el.kind === "chart") {
|
|
@@ -1040,8 +1083,12 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1040
1083
|
};
|
|
1041
1084
|
if (chart.title) add(chart.title);
|
|
1042
1085
|
for (const c of chart.categories) add(c);
|
|
1043
|
-
|
|
1086
|
+
chart.series.forEach((sr, i) => add(legendSeriesName(sr, i)));
|
|
1087
|
+
if (chart.catAxisTitle) add(chart.catAxisTitle);
|
|
1088
|
+
if (chart.valAxisTitle) add(chart.valAxisTitle);
|
|
1089
|
+
for (const sr of chart.series) for (const label of sr.pointLabels ?? []) add(label.text);
|
|
1044
1090
|
add("0123456789.,-%() ");
|
|
1091
|
+
if (chart.numberFormat) add(chart.numberFormat);
|
|
1045
1092
|
}
|
|
1046
1093
|
}
|
|
1047
1094
|
};
|
|
@@ -1049,12 +1096,29 @@ function collectFontResources(body, headersFooters, options) {
|
|
|
1049
1096
|
for (const hf of headersFooters.values()) visit(hf);
|
|
1050
1097
|
for (const note of options.footnotes?.values() ?? []) visit(note);
|
|
1051
1098
|
for (const note of options.endnotes?.values() ?? []) visit(note);
|
|
1052
|
-
for (const comment of options.comments?.values() ?? [])
|
|
1099
|
+
for (const comment of options.comments?.values() ?? []) {
|
|
1100
|
+
visit(comment.content);
|
|
1101
|
+
visit(commentTailBlocks(comment, 1, {
|
|
1102
|
+
parentN: 1,
|
|
1103
|
+
depth: 1,
|
|
1104
|
+
done: true
|
|
1105
|
+
}));
|
|
1106
|
+
visit([{
|
|
1107
|
+
kind: "paragraph",
|
|
1108
|
+
paragraph: {
|
|
1109
|
+
properties: {},
|
|
1110
|
+
runs: [{
|
|
1111
|
+
text: "0123456789",
|
|
1112
|
+
properties: {}
|
|
1113
|
+
}]
|
|
1114
|
+
}
|
|
1115
|
+
}]);
|
|
1116
|
+
}
|
|
1053
1117
|
if (used.size === 0) {
|
|
1054
1118
|
const regular = options.registry.resolveByStyle(false, false);
|
|
1055
1119
|
used.set(regular.variant, {
|
|
1056
1120
|
parsed: regular.parsed,
|
|
1057
|
-
gids: new Set(
|
|
1121
|
+
gids: /* @__PURE__ */ new Set()
|
|
1058
1122
|
});
|
|
1059
1123
|
}
|
|
1060
1124
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -1098,6 +1162,16 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
|
|
|
1098
1162
|
source: paragraph
|
|
1099
1163
|
};
|
|
1100
1164
|
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Word and Excel both draw a super/subscript at about two thirds of the base
|
|
1167
|
+
* size, raised a third of it or dropped a sixth. The base line height does not
|
|
1168
|
+
* change: a superscript inside a line of body text must not open the leading.
|
|
1169
|
+
*/
|
|
1170
|
+
var SCRIPT_SCALE = .66;
|
|
1171
|
+
var SCRIPT_OFFSET = {
|
|
1172
|
+
superscript: 1 / 3,
|
|
1173
|
+
subscript: -1 / 6
|
|
1174
|
+
};
|
|
1101
1175
|
function resolveMathItems(items, fontFor) {
|
|
1102
1176
|
return items.map((it) => {
|
|
1103
1177
|
if (it.kind === "glyph") return {
|
|
@@ -1174,11 +1248,13 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
|
|
|
1174
1248
|
}
|
|
1175
1249
|
const resolvedRun = resolveRunProperties(run.properties, paragraph.properties, options.styles);
|
|
1176
1250
|
const { fontKey } = runFontKeyAndParsed(options, resolvedRun.fontFamily.ascii, resolvedRun.bold, resolvedRun.italic);
|
|
1251
|
+
const script = SCRIPT_OFFSET[resolvedRun.verticalAlign] ?? 0;
|
|
1177
1252
|
return {
|
|
1178
1253
|
run,
|
|
1179
1254
|
resolvedRun,
|
|
1180
1255
|
font: lookupFont(fontResources, fontKey),
|
|
1181
|
-
fontSizePt: resolvedRun.fontSizePt,
|
|
1256
|
+
fontSizePt: script === 0 ? resolvedRun.fontSizePt : resolvedRun.fontSizePt * SCRIPT_SCALE,
|
|
1257
|
+
...script === 0 ? {} : { risePt: resolvedRun.fontSizePt * script },
|
|
1182
1258
|
isImage: false,
|
|
1183
1259
|
imageWidthPt: 0,
|
|
1184
1260
|
imageHeightPt: 0,
|
|
@@ -1233,6 +1309,7 @@ function tokenizePlansLtr(plans) {
|
|
|
1233
1309
|
resolvedRun: plan.resolvedRun,
|
|
1234
1310
|
font: plan.font,
|
|
1235
1311
|
fontSizePt: plan.fontSizePt,
|
|
1312
|
+
...plan.risePt !== void 0 ? { risePt: plan.risePt } : {},
|
|
1236
1313
|
widthPt: plan.font.measure.textWidthPt(t.text, plan.fontSizePt),
|
|
1237
1314
|
bidiLevel: 0
|
|
1238
1315
|
});
|
|
@@ -1288,6 +1365,7 @@ function tokenizePlansBidi(plans, realLevels) {
|
|
|
1288
1365
|
resolvedRun: plan.resolvedRun,
|
|
1289
1366
|
font: plan.font,
|
|
1290
1367
|
fontSizePt: plan.fontSizePt,
|
|
1368
|
+
...plan.risePt !== void 0 ? { risePt: plan.risePt } : {},
|
|
1291
1369
|
widthPt: plan.font.measure.textWidthPt(text, plan.fontSizePt),
|
|
1292
1370
|
bidiLevel: curLevel
|
|
1293
1371
|
});
|
|
@@ -1347,7 +1425,7 @@ function lastCodePoint(s) {
|
|
|
1347
1425
|
const chars = [...s];
|
|
1348
1426
|
return chars.length > 0 ? chars[chars.length - 1].codePointAt(0) : 0;
|
|
1349
1427
|
}
|
|
1350
|
-
function paragraphItemStream(tokens, hyphenator) {
|
|
1428
|
+
function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
1351
1429
|
const entries = [];
|
|
1352
1430
|
let prevBoxEndCp = null;
|
|
1353
1431
|
for (const tok of tokens) {
|
|
@@ -1380,6 +1458,28 @@ function paragraphItemStream(tokens, hyphenator) {
|
|
|
1380
1458
|
prevBoxEndCp = lastCodePoint(tok.text);
|
|
1381
1459
|
const positions = hyphenator ? hyphenator.hyphenate(tok.text) : [];
|
|
1382
1460
|
if (positions.length === 0) {
|
|
1461
|
+
const chunks = maxLineWidthPt !== void 0 && maxLineWidthPt > 0 && tok.widthPt > maxLineWidthPt * 1.1 ? splitToWidth(tok, maxLineWidthPt) : void 0;
|
|
1462
|
+
if (chunks) {
|
|
1463
|
+
chunks.forEach((chunk, ci) => {
|
|
1464
|
+
if (ci > 0) entries.push({
|
|
1465
|
+
item: {
|
|
1466
|
+
type: "penalty",
|
|
1467
|
+
width: 0,
|
|
1468
|
+
penalty: 0,
|
|
1469
|
+
flagged: false
|
|
1470
|
+
},
|
|
1471
|
+
token: null
|
|
1472
|
+
});
|
|
1473
|
+
entries.push({
|
|
1474
|
+
item: {
|
|
1475
|
+
type: "box",
|
|
1476
|
+
width: chunk.widthPt
|
|
1477
|
+
},
|
|
1478
|
+
token: chunk
|
|
1479
|
+
});
|
|
1480
|
+
});
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1383
1483
|
entries.push({
|
|
1384
1484
|
item: {
|
|
1385
1485
|
type: "box",
|
|
@@ -1445,11 +1545,52 @@ function paragraphItemStream(tokens, hyphenator) {
|
|
|
1445
1545
|
});
|
|
1446
1546
|
return entries;
|
|
1447
1547
|
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Cut an unbreakable word into the widest pieces that fit `widthPt`, measured
|
|
1550
|
+
* with the token's own font. Each piece keeps the run's formatting, so the line
|
|
1551
|
+
* builder reassembles them exactly as it does hyphenation fragments.
|
|
1552
|
+
*
|
|
1553
|
+
* @param tok The text token.
|
|
1554
|
+
* @param widthPt The line width to fit each piece into.
|
|
1555
|
+
* @returns The pieces in order, or undefined when even one character overruns
|
|
1556
|
+
* (nothing can be gained by splitting then).
|
|
1557
|
+
*/
|
|
1558
|
+
function splitToWidth(tok, widthPt) {
|
|
1559
|
+
if (tok.kind !== "text") return void 0;
|
|
1560
|
+
const chars = [...tok.text];
|
|
1561
|
+
if (tok.font.measure.textWidthPt(chars.slice(0, MIN_SPLIT_CHARS).join(""), tok.fontSizePt) > widthPt) return;
|
|
1562
|
+
const out = [];
|
|
1563
|
+
let start = 0;
|
|
1564
|
+
while (start < chars.length) {
|
|
1565
|
+
let end = start + 1;
|
|
1566
|
+
let text = chars[start];
|
|
1567
|
+
let width = tok.font.measure.textWidthPt(text, tok.fontSizePt);
|
|
1568
|
+
if (width > widthPt && start === 0 && chars.length === 1) return void 0;
|
|
1569
|
+
while (end < chars.length) {
|
|
1570
|
+
const next = text + chars[end];
|
|
1571
|
+
const nextWidth = tok.font.measure.textWidthPt(next, tok.fontSizePt);
|
|
1572
|
+
if (nextWidth > widthPt) break;
|
|
1573
|
+
text = next;
|
|
1574
|
+
width = nextWidth;
|
|
1575
|
+
end++;
|
|
1576
|
+
}
|
|
1577
|
+
out.push({
|
|
1578
|
+
...tok,
|
|
1579
|
+
text,
|
|
1580
|
+
widthPt: width
|
|
1581
|
+
});
|
|
1582
|
+
start = end;
|
|
1583
|
+
}
|
|
1584
|
+
return out.length > 1 ? out : void 0;
|
|
1585
|
+
}
|
|
1586
|
+
/** The fewest characters a mid-word break may leave on a line. */
|
|
1587
|
+
var MIN_SPLIT_CHARS = 6;
|
|
1448
1588
|
function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved, profile) {
|
|
1449
1589
|
let st = start;
|
|
1450
1590
|
let et = breakIdx;
|
|
1451
|
-
while (st < et && (entries[st].token === null || entries[st].token?.isSpace)) st++;
|
|
1452
|
-
|
|
1591
|
+
while (st < et && (entries[st].token === null || !isFirst && entries[st].token?.isSpace === true)) st++;
|
|
1592
|
+
const paints = (t) => t !== null && t !== void 0 && t.kind === "text" && (t.resolvedRun.underline !== "none" || t.resolvedRun.strike);
|
|
1593
|
+
while (et > st && (entries[et - 1].token === null || entries[et - 1].token?.isSpace === true && !paints(entries[et - 1].token))) et--;
|
|
1453
1594
|
if (st >= et) return null;
|
|
1454
1595
|
const lineTokens = [];
|
|
1455
1596
|
for (let i = st; i < et; i++) {
|
|
@@ -1509,20 +1650,29 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
|
|
|
1509
1650
|
function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile, lineWidths) {
|
|
1510
1651
|
if (tokens.length === 0) return [];
|
|
1511
1652
|
const widths = lineWidths ?? [firstLineWidth, otherWidth];
|
|
1512
|
-
const entries = paragraphItemStream(tokens, hyphenator);
|
|
1653
|
+
const entries = paragraphItemStream(tokens, hyphenator, Math.max(0, ...widths));
|
|
1513
1654
|
const items = entries.map((e) => e.item);
|
|
1514
1655
|
const breaks = profile === "ream" ? breakLines(items, widths).breaks : greedyBreakLines(items, widths);
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1656
|
+
const buildLines = (at) => {
|
|
1657
|
+
const out = [];
|
|
1658
|
+
let start = 0;
|
|
1659
|
+
let isFirst = true;
|
|
1660
|
+
let lineIdx = 0;
|
|
1661
|
+
for (const breakIdx of at) {
|
|
1662
|
+
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(line);
|
|
1665
|
+
start = breakIdx + 1;
|
|
1666
|
+
isFirst = false;
|
|
1667
|
+
lineIdx++;
|
|
1668
|
+
}
|
|
1669
|
+
return out;
|
|
1670
|
+
};
|
|
1671
|
+
let lines = buildLines(breaks);
|
|
1672
|
+
const spill = (ls) => Math.max(0, ...ls.map((l) => l.contentWidthPt - l.availableWidthPt));
|
|
1673
|
+
if (profile === "ream" && lines.length === 1 && spill(lines) > .01) {
|
|
1674
|
+
const greedy = buildLines(greedyBreakLines(items, widths));
|
|
1675
|
+
if (greedy.length > 1 && spill(greedy) < spill(lines) - .01) lines = greedy;
|
|
1526
1676
|
}
|
|
1527
1677
|
if (lines.length > 0) lines[lines.length - 1].isLastInParagraph = true;
|
|
1528
1678
|
return lines;
|
|
@@ -1683,6 +1833,30 @@ function measureSingleLine(paragraph, options, fontResources) {
|
|
|
1683
1833
|
}
|
|
1684
1834
|
return total;
|
|
1685
1835
|
}
|
|
1836
|
+
/**
|
|
1837
|
+
* The above-neighbour a cell resolves its top edge against.
|
|
1838
|
+
*
|
|
1839
|
+
* A spanning cell covers several columns and paints ONE top across all of them,
|
|
1840
|
+
* so taking the first column's neighbour throws away any rule the others
|
|
1841
|
+
* declare. tdf100034.xlsx rules the bottom of its two header cells, and a label
|
|
1842
|
+
* overflowing from two columns to their left spanned over both and wiped that
|
|
1843
|
+
* rule off the page. Take the heaviest bottom under the span instead — it is
|
|
1844
|
+
* the one edge the span can draw, so it should be the strongest claim on it.
|
|
1845
|
+
*/
|
|
1846
|
+
function aboveOfSpan(aboveBordersByCol, colIdx, span) {
|
|
1847
|
+
const first = aboveBordersByCol[colIdx];
|
|
1848
|
+
if (span <= 1) return first;
|
|
1849
|
+
let bottom = first?.bottom;
|
|
1850
|
+
for (let k = 1; k < span; k++) {
|
|
1851
|
+
const b = aboveBordersByCol[colIdx + k]?.bottom;
|
|
1852
|
+
if (heavierBorder(bottom, b) === b) bottom = b;
|
|
1853
|
+
}
|
|
1854
|
+
if (bottom === first?.bottom) return first;
|
|
1855
|
+
return {
|
|
1856
|
+
...first,
|
|
1857
|
+
...bottom ? { bottom } : {}
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1686
1860
|
function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidthsPt, options, fontResources, imageResources, rowMergeRoles, aboveBordersByCol) {
|
|
1687
1861
|
const cells = [];
|
|
1688
1862
|
const columnXOffsets = [];
|
|
@@ -1693,18 +1867,32 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
|
|
|
1693
1867
|
const span = Math.max(1, cell.properties.colSpan ?? 1);
|
|
1694
1868
|
let widthPt = 0;
|
|
1695
1869
|
for (let k = 0; k < span && colIdx + k < columnWidthsPt.length; k++) widthPt += columnWidthsPt[colIdx + k];
|
|
1870
|
+
const paintSpan = Math.min(span, Math.max(1, cell.properties.paintColumns ?? span));
|
|
1871
|
+
let paintWidthPt = 0;
|
|
1872
|
+
for (let k = 0; k < paintSpan && colIdx + k < columnWidthsPt.length; k++) paintWidthPt += columnWidthsPt[colIdx + k];
|
|
1696
1873
|
columnXOffsets.push(cursorX);
|
|
1697
1874
|
const mergeRole = rowMergeRoles[cellIdx] ?? "standalone";
|
|
1698
1875
|
const leftNeighbor = cellIdx > 0 ? row.cells[cellIdx - 1].properties.borders : void 0;
|
|
1699
|
-
const cellLayout = layoutTableCell(cell, tableProps, widthPt, options, fontResources, imageResources, rowIdx, colIdx, span, rowCount, colCount, mergeRole, leftNeighbor, aboveBordersByCol
|
|
1700
|
-
cells.push(
|
|
1876
|
+
const cellLayout = layoutTableCell(cell, tableProps, widthPt, options, fontResources, imageResources, rowIdx, colIdx, span, rowCount, colCount, mergeRole, leftNeighbor, aboveOfSpan(aboveBordersByCol, colIdx, span));
|
|
1877
|
+
cells.push(paintWidthPt < widthPt ? {
|
|
1878
|
+
...cellLayout,
|
|
1879
|
+
paintWidthPt
|
|
1880
|
+
} : cellLayout);
|
|
1701
1881
|
cursorX += widthPt;
|
|
1702
1882
|
colIdx += span;
|
|
1703
1883
|
}
|
|
1704
1884
|
let heightPt = 0;
|
|
1705
1885
|
for (const c of cells) if (c.totalHeightPt > heightPt) heightPt = c.totalHeightPt;
|
|
1706
|
-
if (row.properties.height && row.properties.heightRule === "exact")
|
|
1707
|
-
|
|
1886
|
+
if (row.properties.height && row.properties.heightRule === "exact") {
|
|
1887
|
+
heightPt = row.properties.height;
|
|
1888
|
+
return {
|
|
1889
|
+
heightPt,
|
|
1890
|
+
cells: cells.map((c) => clipCellToHeight(c, heightPt)),
|
|
1891
|
+
columnXOffsets,
|
|
1892
|
+
rowIdx,
|
|
1893
|
+
rowCount
|
|
1894
|
+
};
|
|
1895
|
+
} else if (row.properties.height && row.properties.heightRule === "atLeast") heightPt = Math.max(heightPt, row.properties.height);
|
|
1708
1896
|
return {
|
|
1709
1897
|
heightPt,
|
|
1710
1898
|
cells,
|
|
@@ -1713,6 +1901,35 @@ function layoutTableRow(row, rowIdx, rowCount, colCount, tableProps, columnWidth
|
|
|
1713
1901
|
rowCount
|
|
1714
1902
|
};
|
|
1715
1903
|
}
|
|
1904
|
+
/**
|
|
1905
|
+
* A cell with only the lines that fit inside `heightPt`.
|
|
1906
|
+
*
|
|
1907
|
+
* Whole lines only: half a line of text reads as a rendering fault, where a
|
|
1908
|
+
* missing one reads as the clipping it is. A cell that already fits, or whose
|
|
1909
|
+
* first line does not, is returned untouched — the first line always draws, as
|
|
1910
|
+
* it does in Excel.
|
|
1911
|
+
*
|
|
1912
|
+
* @param cell The laid-out cell.
|
|
1913
|
+
* @param heightPt The row's pinned height.
|
|
1914
|
+
* @returns The cell, clipped.
|
|
1915
|
+
*/
|
|
1916
|
+
function clipCellToHeight(cell, heightPt) {
|
|
1917
|
+
const room = heightPt - cell.padTopPt - cell.padBottomPt;
|
|
1918
|
+
if (cell.lines.length <= 1 || cell.contentHeightPt <= room) return cell;
|
|
1919
|
+
const kept = [];
|
|
1920
|
+
let used = 0;
|
|
1921
|
+
for (const line of cell.lines) {
|
|
1922
|
+
const h = computeLineHeight(line, line.resolved);
|
|
1923
|
+
if (kept.length > 0 && used + h > room) break;
|
|
1924
|
+
kept.push(line);
|
|
1925
|
+
used += h;
|
|
1926
|
+
}
|
|
1927
|
+
return {
|
|
1928
|
+
...cell,
|
|
1929
|
+
lines: kept,
|
|
1930
|
+
contentHeightPt: used
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1716
1933
|
function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imageResources, rowIdx, colStart, colSpan, rowCount, colCount, mergeRole, leftNeighborBorders, aboveNeighborBorders) {
|
|
1717
1934
|
const cellMar = cell.properties.margins;
|
|
1718
1935
|
const tableMar = tableProps.defaultCellMargins;
|
|
@@ -1721,15 +1938,20 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
1721
1938
|
const padLeftBase = cellMar?.left ?? tableMar?.left ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
|
|
1722
1939
|
const padRightBase = cellMar?.right ?? tableMar?.right ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
|
|
1723
1940
|
const padLeftPt = padLeftBase + (cell.properties.icon ? CF_ICON_GUTTER_PT : 0);
|
|
1724
|
-
const padRightPt = padRightBase
|
|
1941
|
+
const padRightPt = padRightBase;
|
|
1725
1942
|
const innerWidth = Math.max(1, widthPt - padLeftPt - padRightPt);
|
|
1726
1943
|
const lines = [];
|
|
1727
1944
|
const nestedTables = [];
|
|
1728
1945
|
let contentHeightPt = 0;
|
|
1946
|
+
let clipped = false;
|
|
1729
1947
|
if (mergeRole !== "middle" && mergeRole !== "end") {
|
|
1730
1948
|
for (const el of cell.content) if (el.kind === "paragraph") {
|
|
1731
|
-
const
|
|
1732
|
-
|
|
1949
|
+
const noWrap = cell.properties.noWrap === true;
|
|
1950
|
+
const block = layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, noWrap ? NO_WRAP_MEASURE_WIDTH : innerWidth);
|
|
1951
|
+
const cutAt = widthPt - padLeftPt;
|
|
1952
|
+
if (noWrap && (block.lines[0]?.contentWidthPt ?? 0) > cutAt) clipped = true;
|
|
1953
|
+
const keep = noWrap ? clipLineToWidth(block.lines, cutAt, cell.properties.hashOnOverflow === true) : block.lines;
|
|
1954
|
+
for (const line of keep) {
|
|
1733
1955
|
lines.push(line);
|
|
1734
1956
|
contentHeightPt += computeLineHeight(line, block.resolved);
|
|
1735
1957
|
}
|
|
@@ -1754,14 +1976,15 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
|
|
|
1754
1976
|
...cell.properties.dataBar ? { dataBar: cell.properties.dataBar } : {},
|
|
1755
1977
|
...cell.properties.icon ? { icon: cell.properties.icon } : {},
|
|
1756
1978
|
...cell.properties.sparkline ? { sparkline: cell.properties.sparkline } : {},
|
|
1757
|
-
...cell.properties.dropdown ? { dropdown: true } : {},
|
|
1758
1979
|
lines,
|
|
1759
1980
|
...nestedTables.length > 0 ? { nestedTables } : {},
|
|
1760
1981
|
contentHeightPt,
|
|
1761
1982
|
totalHeightPt,
|
|
1983
|
+
...cell.properties.verticalAlign ? { verticalAlign: cell.properties.verticalAlign } : {},
|
|
1762
1984
|
colStart,
|
|
1763
1985
|
colSpan,
|
|
1764
|
-
mergeRole
|
|
1986
|
+
mergeRole,
|
|
1987
|
+
...clipped ? { clipped: true } : {}
|
|
1765
1988
|
};
|
|
1766
1989
|
}
|
|
1767
1990
|
var BORDER_STYLE_RANK = {
|
|
@@ -1769,6 +1992,8 @@ var BORDER_STYLE_RANK = {
|
|
|
1769
1992
|
thick: 4,
|
|
1770
1993
|
single: 3,
|
|
1771
1994
|
dashed: 2,
|
|
1995
|
+
dashDot: 2,
|
|
1996
|
+
dashDotDot: 2,
|
|
1772
1997
|
dotted: 1,
|
|
1773
1998
|
none: 0
|
|
1774
1999
|
};
|
|
@@ -1929,6 +2154,14 @@ var PageAssembler = class {
|
|
|
1929
2154
|
* Consumed space counts as use, whether or not any ink went with it.
|
|
1930
2155
|
*/
|
|
1931
2156
|
colHasContent = () => this.current.length > this.colStartLen || this.cursorY < this.colStartY;
|
|
2157
|
+
/**
|
|
2158
|
+
* Whether this page has ANYTHING on it — including drawings, which is the
|
|
2159
|
+
* whole point: a page carrying only floats has an empty `current`, so a page
|
|
2160
|
+
* break asked for after it was silently dropped and the next band's drawings
|
|
2161
|
+
* landed on top of the first's. tdf111980_radioButtons.xlsx has no cells at
|
|
2162
|
+
* all and eleven controls wider than its page.
|
|
2163
|
+
*/
|
|
2164
|
+
pageHasContent = () => this.current.length > 0 || this.floatsBehind.length > 0 || this.floatsFront.length > 0;
|
|
1932
2165
|
/** Overflow step: next column on this page, or a fresh page after the last. */
|
|
1933
2166
|
advanceColumn = () => {
|
|
1934
2167
|
if (this.ctx.columns && this.colIdx + 1 < this.ctx.columns.length) {
|
|
@@ -2114,7 +2347,7 @@ var PageAssembler = class {
|
|
|
2114
2347
|
* guarantee one page for a header/footer-only document).
|
|
2115
2348
|
*/
|
|
2116
2349
|
flushPage = (force = false) => {
|
|
2117
|
-
if (this.
|
|
2350
|
+
if (!this.pageHasContent() && this.cursorY >= this.colStartY && !force) return;
|
|
2118
2351
|
const band = bandForPage(this.pageInSection, this.globalPageIdx, this.ctx.titlePg, this.ctx.evenAndOddHeaders);
|
|
2119
2352
|
const header = pickBand(this.ctx.headerSet, band);
|
|
2120
2353
|
const footer = pickBand(this.ctx.footerSet, band);
|
|
@@ -2174,7 +2407,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2174
2407
|
}
|
|
2175
2408
|
if (builder && !(block.kind === "paragraph" && block.list)) asm.listStack.length = 0;
|
|
2176
2409
|
if (block.kind === "paragraph") {
|
|
2177
|
-
if (block.resolved.pageBreakBefore && asm.
|
|
2410
|
+
if (block.resolved.pageBreakBefore && asm.pageHasContent()) asm.flushPage();
|
|
2178
2411
|
asm.cursorY -= block.spacingBeforePt;
|
|
2179
2412
|
let pb = block;
|
|
2180
2413
|
if (reflowParagraph && pb.source && asm.exclusions.length > 0) {
|
|
@@ -2310,6 +2543,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2310
2543
|
...block.fillColorHex ? { fillColorHex: block.fillColorHex } : {},
|
|
2311
2544
|
...block.fillGradient ? { fillGradient: block.fillGradient } : {},
|
|
2312
2545
|
...block.stroke ? { stroke: block.stroke } : {},
|
|
2546
|
+
...block.shadow ? { shadow: block.shadow } : {},
|
|
2313
2547
|
transform
|
|
2314
2548
|
},
|
|
2315
2549
|
...figId !== void 0 ? { structId: figId } : {}
|
|
@@ -2380,6 +2614,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2380
2614
|
line: t.line,
|
|
2381
2615
|
originX: pt(x + t.x),
|
|
2382
2616
|
baselineY: pt(asm.ctx.pageHeight - (bottomYUp + t.y)),
|
|
2617
|
+
...t.rotationDeg ? { rotationDeg: t.rotationDeg } : {},
|
|
2383
2618
|
...fig
|
|
2384
2619
|
});
|
|
2385
2620
|
};
|
|
@@ -2439,8 +2674,16 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2439
2674
|
asm.cursorY -= hr.heightPt;
|
|
2440
2675
|
}
|
|
2441
2676
|
}
|
|
2442
|
-
|
|
2677
|
+
const mergedHeights = chunks.length === 1 && row.cells.some((c) => c.mergeRole === "start") ? row.cells.map((c) => c.mergeRole === "start" ? mergedBoxHeightPt(block.rows, ri, c.colStart, asm.cursorY - asm.bottomLimit()) : void 0) : void 0;
|
|
2678
|
+
emitRowChunk(asm.current, chunk, tableX, asm.cursorY, asm.ctx.pageHeight, colCount, cellStructIds, mergedHeights);
|
|
2443
2679
|
asm.cursorY -= chunk.heightPt;
|
|
2680
|
+
if (ci === chunks.length - 1 && ri < block.rows.length - 1) {
|
|
2681
|
+
const next = block.rows[ri + 1];
|
|
2682
|
+
const nextHeight = Math.min(next.heightPt, asm.ctx.pageContentHeight);
|
|
2683
|
+
const nextForced = next.breakBefore && ri + 1 >= headerRows.length && asm.current.length > 0;
|
|
2684
|
+
const nextOverflow = asm.cursorY - nextHeight < asm.bottomLimit() && asm.colHasContent();
|
|
2685
|
+
if (nextForced || nextOverflow) emitRowBottomEdge(asm.current, chunk, tableX, asm.cursorY, asm.ctx.pageHeight);
|
|
2686
|
+
}
|
|
2444
2687
|
}
|
|
2445
2688
|
}
|
|
2446
2689
|
}
|
|
@@ -2453,8 +2696,44 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2453
2696
|
if (d.position === "header") page.commands.unshift(...cmds);
|
|
2454
2697
|
else page.commands.push(...cmds);
|
|
2455
2698
|
}
|
|
2699
|
+
for (const page of asm.pages) coalesceVerticalFills(page.commands);
|
|
2456
2700
|
return asm.pages;
|
|
2457
2701
|
}
|
|
2702
|
+
function coalesceVerticalFills(commands) {
|
|
2703
|
+
const touching = (a, b) => Math.abs(a.x - b.x) < .01 && Math.abs(a.width - b.width) < .01 && a.fillColorHex === b.fillColorHex && Math.min(a.y + a.height, b.y + b.height) >= Math.max(a.y, b.y) - .01;
|
|
2704
|
+
const open = /* @__PURE__ */ new Map();
|
|
2705
|
+
const dead = /* @__PURE__ */ new Set();
|
|
2706
|
+
for (const item of commands) {
|
|
2707
|
+
if (item.type !== "fill") continue;
|
|
2708
|
+
const fill = item;
|
|
2709
|
+
const k = `${fill.x.toFixed(2)}|${fill.width.toFixed(2)}|${fill.fillColorHex}`;
|
|
2710
|
+
const prev = open.get(k);
|
|
2711
|
+
if (prev && touching(prev, fill)) {
|
|
2712
|
+
const top = Math.max(prev.y + prev.height, fill.y + fill.height);
|
|
2713
|
+
const bottom = Math.min(prev.y, fill.y);
|
|
2714
|
+
prev.y = pt(bottom);
|
|
2715
|
+
prev.height = pt(top - bottom);
|
|
2716
|
+
dead.add(item);
|
|
2717
|
+
continue;
|
|
2718
|
+
}
|
|
2719
|
+
open.set(k, fill);
|
|
2720
|
+
}
|
|
2721
|
+
if (dead.size === 0) return;
|
|
2722
|
+
const kept = commands.filter((c) => !dead.has(c));
|
|
2723
|
+
commands.length = 0;
|
|
2724
|
+
commands.push(...kept);
|
|
2725
|
+
}
|
|
2726
|
+
function mergedBoxHeightPt(rows, startRowIdx, colStart, availablePt) {
|
|
2727
|
+
let height = rows[startRowIdx].heightPt;
|
|
2728
|
+
for (let r = startRowIdx + 1; r < rows.length; r++) {
|
|
2729
|
+
const cont = rows[r].cells.find((c) => c.colStart === colStart && (c.mergeRole === "middle" || c.mergeRole === "end"));
|
|
2730
|
+
if (!cont) break;
|
|
2731
|
+
if (height + rows[r].heightPt > availablePt) break;
|
|
2732
|
+
height += rows[r].heightPt;
|
|
2733
|
+
if (cont.mergeRole === "end") break;
|
|
2734
|
+
}
|
|
2735
|
+
return height;
|
|
2736
|
+
}
|
|
2458
2737
|
function tableCellRowSpan(rows, startRowIdx, colStart) {
|
|
2459
2738
|
let span = 1;
|
|
2460
2739
|
for (let r = startRowIdx + 1; r < rows.length; r++) {
|
|
@@ -2465,32 +2744,74 @@ function tableCellRowSpan(rows, startRowIdx, colStart) {
|
|
|
2465
2744
|
}
|
|
2466
2745
|
return span;
|
|
2467
2746
|
}
|
|
2468
|
-
function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellStructIds) {
|
|
2747
|
+
function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellStructIds, mergedHeights) {
|
|
2469
2748
|
const rowTop = cursorY;
|
|
2470
2749
|
const rowBottom = cursorY - row.heightPt;
|
|
2750
|
+
{
|
|
2751
|
+
let i = 0;
|
|
2752
|
+
while (i < row.cells.length) {
|
|
2753
|
+
const cell = row.cells[i];
|
|
2754
|
+
if (!cell.shadingColorHex) {
|
|
2755
|
+
i++;
|
|
2756
|
+
continue;
|
|
2757
|
+
}
|
|
2758
|
+
let width = cell.paintWidthPt ?? cell.widthPt;
|
|
2759
|
+
let j = i + 1;
|
|
2760
|
+
while (j < row.cells.length && cell.paintWidthPt === void 0) {
|
|
2761
|
+
const next = row.cells[j];
|
|
2762
|
+
if (next.mergeRole === "middle" || next.mergeRole === "end") break;
|
|
2763
|
+
if (next.shadingColorHex !== cell.shadingColorHex) break;
|
|
2764
|
+
width += next.paintWidthPt ?? next.widthPt;
|
|
2765
|
+
j++;
|
|
2766
|
+
}
|
|
2767
|
+
out.push({
|
|
2768
|
+
type: "fill",
|
|
2769
|
+
x: pt(marginLeft + (row.columnXOffsets[i] ?? 0)),
|
|
2770
|
+
y: pt(pageHeight - rowBottom - row.heightPt - FILL_SEAM_PT),
|
|
2771
|
+
width: pt(width),
|
|
2772
|
+
height: pt(row.heightPt + FILL_SEAM_PT),
|
|
2773
|
+
fillColorHex: cell.shadingColorHex
|
|
2774
|
+
});
|
|
2775
|
+
i = j;
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2471
2778
|
for (let i = 0; i < row.cells.length; i++) {
|
|
2472
2779
|
const cell = row.cells[i];
|
|
2473
2780
|
const cellX = marginLeft + (row.columnXOffsets[i] ?? 0);
|
|
2474
2781
|
const structId = cellStructIds?.[i];
|
|
2475
|
-
if (cell.shadingColorHex && cell.mergeRole !== "middle" && cell.mergeRole !== "end") out.push({
|
|
2476
|
-
type: "fill",
|
|
2477
|
-
x: pt(cellX),
|
|
2478
|
-
y: pt(pageHeight - rowBottom - row.heightPt),
|
|
2479
|
-
width: pt(cell.widthPt),
|
|
2480
|
-
height: pt(row.heightPt),
|
|
2481
|
-
fillColorHex: cell.shadingColorHex
|
|
2482
|
-
});
|
|
2483
2782
|
if (cell.dataBar && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
|
|
2484
2783
|
const start = Math.max(0, Math.min(1, cell.dataBar.startFraction ?? 0));
|
|
2485
2784
|
const barWidth = cell.widthPt * Math.max(0, Math.min(1, cell.dataBar.fraction));
|
|
2486
|
-
if (barWidth > 0)
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2785
|
+
if (barWidth > 0) {
|
|
2786
|
+
const barX = cellX + cell.widthPt * start;
|
|
2787
|
+
const barBottomYUp = rowBottom;
|
|
2788
|
+
out.push({
|
|
2789
|
+
type: "shape",
|
|
2790
|
+
shape: {
|
|
2791
|
+
paths: [rectAtPath(0, 0, barWidth, row.heightPt)],
|
|
2792
|
+
fillColorHex: cell.dataBar.colorHex,
|
|
2793
|
+
fillGradient: {
|
|
2794
|
+
kind: "linear",
|
|
2795
|
+
angle: cell.dataBar.negative ? 180 : 0,
|
|
2796
|
+
stops: [{
|
|
2797
|
+
offset: 0,
|
|
2798
|
+
colorHex: cell.dataBar.colorHex
|
|
2799
|
+
}, {
|
|
2800
|
+
offset: 1,
|
|
2801
|
+
colorHex: "FFFFFF"
|
|
2802
|
+
}]
|
|
2803
|
+
},
|
|
2804
|
+
transform: flipTransform([
|
|
2805
|
+
1,
|
|
2806
|
+
0,
|
|
2807
|
+
0,
|
|
2808
|
+
1,
|
|
2809
|
+
barX,
|
|
2810
|
+
barBottomYUp
|
|
2811
|
+
], pageHeight)
|
|
2812
|
+
}
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2494
2815
|
}
|
|
2495
2816
|
if (cell.icon && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
|
|
2496
2817
|
const iconSize = Math.min(CF_ICON_SIZE_PT, Math.max(0, row.heightPt - 1));
|
|
@@ -2592,16 +2913,26 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
|
|
|
2592
2913
|
if (diagUp) pushDiagonal(diagUp, 0, h);
|
|
2593
2914
|
}
|
|
2594
2915
|
if (cell.mergeRole === "middle" || cell.mergeRole === "end") continue;
|
|
2595
|
-
|
|
2916
|
+
const boxHeightPt = mergedHeights?.[i] ?? row.heightPt;
|
|
2917
|
+
const slack = cell.verticalAlign !== void 0 ? boxHeightPt - cell.padTopPt - cell.contentHeightPt - cell.padBottomPt : 0;
|
|
2918
|
+
const vOffset = slack <= 0 ? 0 : cell.verticalAlign === "bottom" ? slack : cell.verticalAlign === "center" ? slack / 2 : 0;
|
|
2919
|
+
let textY = rowTop - cell.padTopPt - vOffset;
|
|
2596
2920
|
for (const line of cell.lines) {
|
|
2597
2921
|
const h = computeLineHeight(line, line.resolved);
|
|
2598
2922
|
textY -= h;
|
|
2599
2923
|
const offset = alignmentOffset(line.resolved.alignment, line.contentWidthPt, cell.widthPt - cell.padLeftPt - cell.padRightPt);
|
|
2924
|
+
const indentLeft = line.resolved.indentLeft + (line.firstLine ? line.resolved.indentFirstLine : 0);
|
|
2600
2925
|
out.push({
|
|
2601
2926
|
type: "line",
|
|
2602
2927
|
line,
|
|
2603
|
-
originX: pt(cellX + cell.padLeftPt + offset),
|
|
2928
|
+
originX: pt(cellX + cell.padLeftPt + indentLeft + offset),
|
|
2604
2929
|
baselineY: pt(pageHeight - (textY + lineDescent(line))),
|
|
2930
|
+
...cell.clipped ? { clip: {
|
|
2931
|
+
x: pt(cellX),
|
|
2932
|
+
y: pt(pageHeight - rowTop),
|
|
2933
|
+
width: pt(cell.widthPt),
|
|
2934
|
+
height: pt(boxHeightPt)
|
|
2935
|
+
} } : {},
|
|
2605
2936
|
...structId !== void 0 ? { structId } : {}
|
|
2606
2937
|
});
|
|
2607
2938
|
}
|
|
@@ -2697,6 +3028,7 @@ function splitRowIntoChunks(row, capacity) {
|
|
|
2697
3028
|
}
|
|
2698
3029
|
return out.length > 0 ? out : [row];
|
|
2699
3030
|
}
|
|
3031
|
+
var FILL_SEAM_PT = .07;
|
|
2700
3032
|
function emitCellBorders(out, cell, cellX, cellY, rowHeight, pageHeight, rowIdx, rowCount, colCount) {
|
|
2701
3033
|
const pushSide = (side, border) => {
|
|
2702
3034
|
if (!border || border.style === "none") return;
|
|
@@ -2706,10 +3038,11 @@ function emitCellBorders(out, cell, cellX, cellY, rowHeight, pageHeight, rowIdx,
|
|
|
2706
3038
|
side,
|
|
2707
3039
|
x: pt(cellX),
|
|
2708
3040
|
y: pt(pageHeight - cellY - rowHeight),
|
|
2709
|
-
width: pt(cell.widthPt),
|
|
3041
|
+
width: pt(cell.paintWidthPt ?? cell.widthPt),
|
|
2710
3042
|
height: pt(rowHeight),
|
|
2711
3043
|
borderSizePt: sz,
|
|
2712
|
-
borderColorHex: border.colorHex ?? "000000"
|
|
3044
|
+
borderColorHex: border.colorHex ?? "000000",
|
|
3045
|
+
...border.style !== "single" ? { borderStyle: border.style } : {}
|
|
2713
3046
|
});
|
|
2714
3047
|
};
|
|
2715
3048
|
pushSide("top", cell.borders.top);
|
|
@@ -2717,5 +3050,101 @@ function emitCellBorders(out, cell, cellX, cellY, rowHeight, pageHeight, rowIdx,
|
|
|
2717
3050
|
if (rowIdx === rowCount - 1) pushSide("bottom", cell.borders.bottom);
|
|
2718
3051
|
if (cell.colStart + cell.colSpan - 1 === colCount - 1) pushSide("right", cell.borders.right);
|
|
2719
3052
|
}
|
|
3053
|
+
var NO_WRAP_MEASURE_WIDTH = 1e6;
|
|
3054
|
+
/**
|
|
3055
|
+
* One line, trimmed to the glyph that STRADDLES `widthPt` — what a non-wrapping
|
|
3056
|
+
* spreadsheet cell shows. Excel and LibreOffice paint that glyph and cut it
|
|
3057
|
+
* through the middle, and so do we now: the caller marks the cell and the
|
|
3058
|
+
* emitters clip the line to its box (TextLineItem.clip). Stopping at the last
|
|
3059
|
+
* glyph that wholly fits is what dropped a character off every cut cell.
|
|
3060
|
+
*/
|
|
3061
|
+
function clipLineToWidth(lines, widthPt, hashOnOverflow = false) {
|
|
3062
|
+
const line = lines[0];
|
|
3063
|
+
if (!line) return [];
|
|
3064
|
+
if (line.contentWidthPt <= widthPt) return [line];
|
|
3065
|
+
if (hashOnOverflow) return [hashFill(line, widthPt)];
|
|
3066
|
+
const tokens = [];
|
|
3067
|
+
let used = 0;
|
|
3068
|
+
for (const token of line.tokens) {
|
|
3069
|
+
const advance = token.widthPt;
|
|
3070
|
+
if (used + advance <= widthPt) {
|
|
3071
|
+
tokens.push(token);
|
|
3072
|
+
used += advance;
|
|
3073
|
+
continue;
|
|
3074
|
+
}
|
|
3075
|
+
if (token.kind === "text") {
|
|
3076
|
+
const room = widthPt - used;
|
|
3077
|
+
let text = "";
|
|
3078
|
+
let textWidth = 0;
|
|
3079
|
+
for (const ch of token.text) {
|
|
3080
|
+
if (textWidth >= room) break;
|
|
3081
|
+
text += ch;
|
|
3082
|
+
textWidth = token.font.measure.textWidthPt(text, token.fontSizePt);
|
|
3083
|
+
}
|
|
3084
|
+
if (text.length > 0) {
|
|
3085
|
+
tokens.push({
|
|
3086
|
+
...token,
|
|
3087
|
+
text,
|
|
3088
|
+
widthPt: textWidth
|
|
3089
|
+
});
|
|
3090
|
+
used += textWidth;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
break;
|
|
3094
|
+
}
|
|
3095
|
+
return [{
|
|
3096
|
+
...line,
|
|
3097
|
+
tokens,
|
|
3098
|
+
contentWidthPt: used
|
|
3099
|
+
}];
|
|
3100
|
+
}
|
|
3101
|
+
/**
|
|
3102
|
+
* The line as a row of `#` filling `widthPt` — what Excel and LibreOffice put in
|
|
3103
|
+
* a cell whose number does not fit. At least one, so the cell is never blank:
|
|
3104
|
+
* a column too narrow even for a single `#` still has to say something.
|
|
3105
|
+
*/
|
|
3106
|
+
function hashFill(line, widthPt) {
|
|
3107
|
+
const first = line.tokens.find((t) => t.kind === "text");
|
|
3108
|
+
if (!first) return line;
|
|
3109
|
+
const one = first.font.measure.textWidthPt("#", first.fontSizePt);
|
|
3110
|
+
const count = one > 0 ? Math.max(1, Math.floor(widthPt / one)) : 1;
|
|
3111
|
+
const text = "#".repeat(count);
|
|
3112
|
+
const token = {
|
|
3113
|
+
...first,
|
|
3114
|
+
text,
|
|
3115
|
+
widthPt: first.font.measure.textWidthPt(text, first.fontSizePt)
|
|
3116
|
+
};
|
|
3117
|
+
return {
|
|
3118
|
+
...line,
|
|
3119
|
+
tokens: [token],
|
|
3120
|
+
contentWidthPt: token.widthPt
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
/**
|
|
3124
|
+
* The bottom edge of a row that ends a page — the rule the row below it owed it
|
|
3125
|
+
* and drew on the next page instead. See the call site: a table that spills over
|
|
3126
|
+
* a break is closed on both sides of it, the way Excel and Word print one.
|
|
3127
|
+
*
|
|
3128
|
+
* A cell whose bottom is suppressed (a vertical merge continuing past the break)
|
|
3129
|
+
* stays open; inventing an edge there would cut the merge in half.
|
|
3130
|
+
*/
|
|
3131
|
+
function emitRowBottomEdge(out, row, marginLeft, rowBottom, pageHeight) {
|
|
3132
|
+
for (let i = 0; i < row.cells.length; i++) {
|
|
3133
|
+
const cell = row.cells[i];
|
|
3134
|
+
const border = cell.borders.bottom;
|
|
3135
|
+
if (!border || border.style === "none") continue;
|
|
3136
|
+
out.push({
|
|
3137
|
+
type: "border",
|
|
3138
|
+
side: "bottom",
|
|
3139
|
+
x: pt(marginLeft + (row.columnXOffsets[i] ?? 0)),
|
|
3140
|
+
y: pt(pageHeight - rowBottom - row.heightPt),
|
|
3141
|
+
width: pt(cell.widthPt),
|
|
3142
|
+
height: pt(row.heightPt),
|
|
3143
|
+
borderSizePt: border.width ?? DEFAULT_BORDER_SIZE_EIGHTH * EIGHTH_PT,
|
|
3144
|
+
borderColorHex: border.colorHex ?? "000000",
|
|
3145
|
+
...border.style !== "single" ? { borderStyle: border.style } : {}
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
2720
3149
|
//#endregion
|
|
2721
3150
|
export { layoutStyledDocument };
|