pagyra-js 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +283 -264
- package/dist/browser/pagyra.min.js +30 -30
- package/dist/browser/pagyra.min.js.map +4 -4
- package/dist/src/css/apply-declarations.js +2 -1
- package/dist/src/css/clip-path-types.d.ts +9 -1
- package/dist/src/css/compute-style/overrides.js +10 -1
- package/dist/src/css/parsers/clip-path-parser.js +51 -0
- package/dist/src/css/parsers/register-parsers.js +21 -0
- package/dist/src/css/properties/visual.d.ts +2 -0
- package/dist/src/css/style.d.ts +5 -0
- package/dist/src/css/style.js +3 -0
- package/dist/src/css/ua-defaults/element-defaults.js +13 -0
- package/dist/src/dom/node.d.ts +2 -0
- package/dist/src/dom/node.js +1 -0
- package/dist/src/fonts/woff2/decoder.d.ts +1 -9
- package/dist/src/fonts/woff2/decoder.js +6 -565
- package/dist/src/fonts/woff2/glyf-reconstructor.d.ts +54 -0
- package/dist/src/fonts/woff2/glyf-reconstructor.js +357 -0
- package/dist/src/fonts/woff2/hmtx-reconstructor.d.ts +5 -0
- package/dist/src/fonts/woff2/hmtx-reconstructor.js +42 -0
- package/dist/src/fonts/woff2/sfnt-builder.d.ts +7 -0
- package/dist/src/fonts/woff2/sfnt-builder.js +55 -0
- package/dist/src/fonts/woff2/utils.d.ts +12 -0
- package/dist/src/fonts/woff2/utils.js +111 -0
- package/dist/src/html-to-pdf/render-finalize.js +5 -1
- package/dist/src/layout/inline/run-placer.js +1 -1
- package/dist/src/layout/strategies/flex/alignment.d.ts +10 -0
- package/dist/src/layout/strategies/flex/alignment.js +91 -0
- package/dist/src/layout/strategies/flex/distributor.d.ts +5 -0
- package/dist/src/layout/strategies/flex/distributor.js +56 -0
- package/dist/src/layout/strategies/flex/line-builder.d.ts +5 -0
- package/dist/src/layout/strategies/flex/line-builder.js +55 -0
- package/dist/src/layout/strategies/flex/types.d.ts +27 -0
- package/dist/src/layout/strategies/flex/types.js +2 -0
- package/dist/src/layout/strategies/flex/utils.d.ts +12 -0
- package/dist/src/layout/strategies/flex/utils.js +113 -0
- package/dist/src/layout/strategies/flex.js +4 -308
- package/dist/src/layout/strategies/grid.js +0 -3
- package/dist/src/layout/strategies/table.js +85 -58
- package/dist/src/layout/utils/text-metrics.js +16 -8
- package/dist/src/pdf/font/embedder.js +3 -3
- package/dist/src/pdf/font/font-subset.js +1 -3
- package/dist/src/pdf/font/to-unicode.js +16 -16
- package/dist/src/pdf/layout-tree-builder.js +15 -9
- package/dist/src/pdf/renderer/box-painter.js +74 -9
- package/dist/src/pdf/renderers/text-renderer.d.ts +4 -2
- package/dist/src/pdf/renderers/text-renderer.js +52 -2
- package/dist/src/pdf/types.d.ts +16 -1
- package/dist/src/pdf/utils/clip-path-resolver.js +28 -12
- package/dist/src/pdf/utils/mask-resolver.d.ts +7 -0
- package/dist/src/pdf/utils/mask-resolver.js +25 -0
- package/dist/src/pdf/utils/node-text-run-factory.d.ts +2 -1
- package/dist/src/pdf/utils/node-text-run-factory.js +5 -26
- package/dist/src/pdf/utils/rounded-rect-to-path.d.ts +7 -0
- package/dist/src/pdf/utils/rounded-rect-to-path.js +86 -0
- package/dist/src/render/offset.d.ts +5 -0
- package/dist/src/render/offset.js +93 -9
- package/dist/src/text/line-breaker.js +31 -0
- package/dist/tests/css/clip-path-parser.spec.js +15 -8
- package/dist/tests/environment/path-resolution.spec.js +2 -1
- package/dist/tests/helpers/ai-layout-diagnostics.js +6 -6
- package/dist/tests/layout/container-query-units.spec.js +0 -7
- package/dist/tests/layout/inline-background-alignment.spec.js +6 -6
- package/dist/tests/layout/table-image-cell.spec.js +95 -0
- package/dist/tests/pdf/alignments.spec.js +12 -12
- package/dist/tests/pdf/clip-path.spec.js +3 -1
- package/dist/tests/pdf/form-text-encoding.spec.js +1 -1
- package/dist/tests/pdf/svg-stroke-dash.spec.js +8 -8
- package/dist/tests/pdf/text-transform-matrix.spec.js +1 -1
- package/dist/tests/pdf/xref-integrity.spec.js +1 -1
- package/dist/tests/verify-subset-multi.spec.js +14 -14
- package/dist/tests/verify-subset.spec.js +12 -12
- package/package.json +89 -71
- package/dist/src/image/js-png-backend.d.ts +0 -7
- package/dist/src/image/js-png-backend.js +0 -9
- package/dist/src/image/png-backend.d.ts +0 -5
- package/dist/src/image/png-wasm-loader.d.ts +0 -5
- package/dist/src/image/png-wasm-loader.js +0 -59
- package/dist/src/image/wasm/png_decoder_wasm.d.ts +0 -8
- package/dist/src/image/wasm/png_decoder_wasm.js +0 -24
- package/dist/src/image/wasm/png_decoder_wasm_bg.js +0 -16
- package/dist/src/image/wasm-png-backend.d.ts +0 -6
- package/dist/src/image/wasm-png-backend.js +0 -17
- package/dist/src/layout/table/cell_layout.d.ts +0 -2
- package/dist/src/layout/table/cell_layout.js +0 -26
- package/dist/tests/image/png-backend.spec.d.ts +0 -1
- package/dist/tests/image/png-backend.spec.js +0 -34
- package/dist/tests/pdf/font-subset-registry-key.spec.d.ts +0 -1
- package/dist/tests/pdf/font-subset-registry-key.spec.js +0 -66
- package/dist/tests/pdf/header-footer.spec.d.ts +0 -1
- package/dist/tests/pdf/header-footer.spec.js +0 -46
- /package/dist/{src/image/png-backend.js → tests/layout/table-image-cell.spec.d.ts} +0 -0
|
@@ -3,183 +3,10 @@ import { AlignItems, Display, JustifyContent } from "../../css/enums.js";
|
|
|
3
3
|
import { adjustForBoxSizing, containingBlock, resolveBoxMetrics, resolveWidthBlock } from "../utils/node-math.js";
|
|
4
4
|
import { resolveLength, isAutoLength } from "../../css/length.js";
|
|
5
5
|
import { GapCalculator, calculateTotalGap } from "../utils/gap-calculator.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return Display.Block;
|
|
11
|
-
case Display.InlineFlex:
|
|
12
|
-
return Display.Flex;
|
|
13
|
-
case Display.InlineGrid:
|
|
14
|
-
return Display.Grid;
|
|
15
|
-
case Display.InlineTable:
|
|
16
|
-
return Display.Table;
|
|
17
|
-
default:
|
|
18
|
-
return display;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function allowsPreferredShrink(originalDisplay, effectiveDisplay) {
|
|
22
|
-
switch (effectiveDisplay) {
|
|
23
|
-
case Display.Flex:
|
|
24
|
-
case Display.InlineFlex:
|
|
25
|
-
case Display.Grid:
|
|
26
|
-
case Display.InlineGrid:
|
|
27
|
-
return false;
|
|
28
|
-
default:
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
if (effectiveDisplay === originalDisplay) {
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
return originalDisplay === Display.Inline;
|
|
35
|
-
}
|
|
36
|
-
function buildFlexLines(items, containerMainSize, mainAxisGap) {
|
|
37
|
-
if (items.length === 0) {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
const lines = [];
|
|
41
|
-
let current = { items: [], mainSizeWithGaps: 0, crossSize: 0 };
|
|
42
|
-
for (const item of items) {
|
|
43
|
-
const addition = current.items.length === 0 ? item.mainContribution : mainAxisGap + item.mainContribution;
|
|
44
|
-
const shouldWrap = current.items.length > 0 &&
|
|
45
|
-
containerMainSize > 0 &&
|
|
46
|
-
current.mainSizeWithGaps + addition > containerMainSize + 0.01;
|
|
47
|
-
if (shouldWrap) {
|
|
48
|
-
lines.push(current);
|
|
49
|
-
current = { items: [], mainSizeWithGaps: 0, crossSize: 0 };
|
|
50
|
-
}
|
|
51
|
-
if (current.items.length > 0) {
|
|
52
|
-
current.mainSizeWithGaps += mainAxisGap;
|
|
53
|
-
}
|
|
54
|
-
current.items.push(item);
|
|
55
|
-
current.mainSizeWithGaps += item.mainContribution;
|
|
56
|
-
current.crossSize = Math.max(current.crossSize, item.crossContribution);
|
|
57
|
-
}
|
|
58
|
-
if (current.items.length > 0) {
|
|
59
|
-
lines.push(current);
|
|
60
|
-
}
|
|
61
|
-
return lines;
|
|
62
|
-
}
|
|
63
|
-
function calculateLinesCrossSize(lines, crossAxisGap) {
|
|
64
|
-
if (lines.length === 0) {
|
|
65
|
-
return 0;
|
|
66
|
-
}
|
|
67
|
-
const totalLines = lines.reduce((sum, line) => sum + line.crossSize, 0);
|
|
68
|
-
return totalLines + calculateTotalGap(crossAxisGap, lines.length);
|
|
69
|
-
}
|
|
70
|
-
function refreshFlexItemSizes(item, isRow) {
|
|
71
|
-
item.mainSize = isRow ? item.node.box.borderBoxWidth : item.node.box.borderBoxHeight;
|
|
72
|
-
item.crossSize = isRow ? item.node.box.borderBoxHeight : item.node.box.borderBoxWidth;
|
|
73
|
-
item.mainContribution = item.mainSize + item.mainMarginStart + item.mainMarginEnd;
|
|
74
|
-
item.crossContribution = item.crossSize + item.crossMarginStart + item.crossMarginEnd;
|
|
75
|
-
}
|
|
76
|
-
function recomputeLineMetrics(line, mainAxisGap) {
|
|
77
|
-
let mainSizeWithGaps = 0;
|
|
78
|
-
let crossSize = 0;
|
|
79
|
-
for (let i = 0; i < line.items.length; i++) {
|
|
80
|
-
const item = line.items[i];
|
|
81
|
-
if (i > 0) {
|
|
82
|
-
mainSizeWithGaps += mainAxisGap;
|
|
83
|
-
}
|
|
84
|
-
mainSizeWithGaps += item.mainContribution;
|
|
85
|
-
crossSize = Math.max(crossSize, item.crossContribution);
|
|
86
|
-
}
|
|
87
|
-
line.mainSizeWithGaps = mainSizeWithGaps;
|
|
88
|
-
line.crossSize = crossSize;
|
|
89
|
-
}
|
|
90
|
-
function relayoutFlexItemForMainSize(container, item, context, targetMainSize, isRow) {
|
|
91
|
-
if (!Number.isFinite(targetMainSize) || targetMainSize < 0) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const prevContainerWidth = container.box.contentWidth;
|
|
95
|
-
const prevContainerHeight = container.box.contentHeight;
|
|
96
|
-
const targetContribution = targetMainSize + item.mainMarginStart + item.mainMarginEnd;
|
|
97
|
-
let displayMutated = false;
|
|
98
|
-
if (item.node.style.display !== item.effectiveDisplay) {
|
|
99
|
-
item.node.style.display = item.effectiveDisplay;
|
|
100
|
-
displayMutated = true;
|
|
101
|
-
}
|
|
102
|
-
try {
|
|
103
|
-
if (isRow) {
|
|
104
|
-
container.box.contentWidth = Math.max(0, targetContribution);
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
container.box.contentHeight = Math.max(0, targetContribution);
|
|
108
|
-
}
|
|
109
|
-
context.layoutChild(item.node);
|
|
110
|
-
}
|
|
111
|
-
finally {
|
|
112
|
-
container.box.contentWidth = prevContainerWidth;
|
|
113
|
-
container.box.contentHeight = prevContainerHeight;
|
|
114
|
-
if (displayMutated) {
|
|
115
|
-
item.node.style.display = item.originalDisplay;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function distributeFlexGrowAcrossLines(lines, container, context, containerMainSize, mainAxisGap, isRow) {
|
|
120
|
-
if (lines.length === 0 || !(containerMainSize > 0)) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
for (const line of lines) {
|
|
124
|
-
const freeSpace = containerMainSize - line.mainSizeWithGaps;
|
|
125
|
-
if (!(freeSpace > 0)) {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
const growItems = line.items.filter((item) => item.flexGrow > 0);
|
|
129
|
-
const totalGrow = growItems.reduce((sum, item) => sum + item.flexGrow, 0);
|
|
130
|
-
if (!(totalGrow > 0)) {
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
for (const item of growItems) {
|
|
134
|
-
const delta = (freeSpace * item.flexGrow) / totalGrow;
|
|
135
|
-
const targetMainSize = Math.max(0, item.mainSize + delta);
|
|
136
|
-
if (Math.abs(targetMainSize - item.mainSize) < 0.01) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
relayoutFlexItemForMainSize(container, item, context, targetMainSize, isRow);
|
|
140
|
-
refreshFlexItemSizes(item, isRow);
|
|
141
|
-
}
|
|
142
|
-
recomputeLineMetrics(line, mainAxisGap);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
function resolveAlignContentLayout(lines, alignContent, containerCrossSize, crossAxisGap) {
|
|
146
|
-
const lineCrossSizes = lines.map((line) => line.crossSize);
|
|
147
|
-
if (lines.length === 0) {
|
|
148
|
-
return { lineCrossSizes, initialOffset: 0, additionalGap: 0 };
|
|
149
|
-
}
|
|
150
|
-
const naturalCross = calculateLinesCrossSize(lines, crossAxisGap);
|
|
151
|
-
const freeSpace = Math.max(0, containerCrossSize - naturalCross);
|
|
152
|
-
switch (alignContent) {
|
|
153
|
-
case "stretch":
|
|
154
|
-
if (freeSpace > 0) {
|
|
155
|
-
const extraPerLine = freeSpace / lines.length;
|
|
156
|
-
for (let i = 0; i < lineCrossSizes.length; i++) {
|
|
157
|
-
lineCrossSizes[i] += extraPerLine;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return { lineCrossSizes, initialOffset: 0, additionalGap: 0 };
|
|
161
|
-
case "flex-end":
|
|
162
|
-
return { lineCrossSizes, initialOffset: freeSpace, additionalGap: 0 };
|
|
163
|
-
case "center":
|
|
164
|
-
return { lineCrossSizes, initialOffset: freeSpace / 2, additionalGap: 0 };
|
|
165
|
-
case "space-between":
|
|
166
|
-
if (lines.length <= 1) {
|
|
167
|
-
return { lineCrossSizes, initialOffset: 0, additionalGap: 0 };
|
|
168
|
-
}
|
|
169
|
-
return { lineCrossSizes, initialOffset: 0, additionalGap: freeSpace / (lines.length - 1) };
|
|
170
|
-
case "space-around": {
|
|
171
|
-
const gap = freeSpace / lines.length;
|
|
172
|
-
return { lineCrossSizes, initialOffset: gap / 2, additionalGap: gap };
|
|
173
|
-
}
|
|
174
|
-
case "space-evenly": {
|
|
175
|
-
const gap = freeSpace / (lines.length + 1);
|
|
176
|
-
return { lineCrossSizes, initialOffset: gap, additionalGap: gap };
|
|
177
|
-
}
|
|
178
|
-
case "flex-start":
|
|
179
|
-
default:
|
|
180
|
-
return { lineCrossSizes, initialOffset: 0, additionalGap: 0 };
|
|
181
|
-
}
|
|
182
|
-
}
|
|
6
|
+
import { blockifyFlexItemDisplay, allowsPreferredShrink, isRowDirection, isAutoMainSize, computePreferredInlineWidth, offsetLayoutSubtree, resolveInitialDimension, resolveFlexSize } from "./flex/utils.js";
|
|
7
|
+
import { buildFlexLines, calculateLinesCrossSize } from "./flex/line-builder.js";
|
|
8
|
+
import { distributeFlexGrowAcrossLines } from "./flex/distributor.js";
|
|
9
|
+
import { resolveAlignContentLayout, resolveJustifySpacing, computeCrossOffset, resolveItemAlignment } from "./flex/alignment.js";
|
|
183
10
|
export class FlexLayoutStrategy {
|
|
184
11
|
constructor() {
|
|
185
12
|
this.supportedDisplays = new Set([Display.Flex, Display.InlineFlex]);
|
|
@@ -521,134 +348,3 @@ export class FlexLayoutStrategy {
|
|
|
521
348
|
node.box.scrollHeight = node.box.contentHeight;
|
|
522
349
|
}
|
|
523
350
|
}
|
|
524
|
-
function resolveInitialDimension(specified, fallback) {
|
|
525
|
-
if (specified !== undefined && Number.isFinite(specified)) {
|
|
526
|
-
return Math.max(specified, 0);
|
|
527
|
-
}
|
|
528
|
-
if (Number.isFinite(fallback)) {
|
|
529
|
-
return Math.max(fallback, 0);
|
|
530
|
-
}
|
|
531
|
-
return 0;
|
|
532
|
-
}
|
|
533
|
-
function resolveFlexSize(value, reference, containerWidth = reference, containerHeight = reference) {
|
|
534
|
-
if (value === undefined) {
|
|
535
|
-
return undefined;
|
|
536
|
-
}
|
|
537
|
-
if (typeof value === "number") {
|
|
538
|
-
return value;
|
|
539
|
-
}
|
|
540
|
-
if (value === "auto" || isAutoLength(value)) {
|
|
541
|
-
return undefined;
|
|
542
|
-
}
|
|
543
|
-
return resolveLength(value, reference, { auto: "reference", containerWidth, containerHeight });
|
|
544
|
-
}
|
|
545
|
-
function resolveItemAlignment(alignSelf, containerAlign) {
|
|
546
|
-
if (alignSelf && alignSelf !== "auto") {
|
|
547
|
-
return alignSelf;
|
|
548
|
-
}
|
|
549
|
-
return containerAlign;
|
|
550
|
-
}
|
|
551
|
-
function computeCrossOffset(alignment, containerSize, itemSize, marginStart, marginEnd) {
|
|
552
|
-
const total = itemSize + marginStart + marginEnd;
|
|
553
|
-
const reference = Number.isFinite(containerSize) ? containerSize : total;
|
|
554
|
-
const freeSpace = reference - total;
|
|
555
|
-
if (freeSpace <= 0) {
|
|
556
|
-
return 0;
|
|
557
|
-
}
|
|
558
|
-
switch (alignment) {
|
|
559
|
-
case AlignItems.Center:
|
|
560
|
-
return freeSpace / 2;
|
|
561
|
-
case AlignItems.FlexEnd:
|
|
562
|
-
return freeSpace;
|
|
563
|
-
default:
|
|
564
|
-
return 0;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
function resolveJustifySpacing(justify, freeSpace, itemCount) {
|
|
568
|
-
if (itemCount <= 0) {
|
|
569
|
-
return { offset: 0, gap: 0 };
|
|
570
|
-
}
|
|
571
|
-
const clamped = Number.isFinite(freeSpace) ? Math.max(freeSpace, 0) : 0;
|
|
572
|
-
switch (justify) {
|
|
573
|
-
case JustifyContent.Center:
|
|
574
|
-
return { offset: clamped / 2, gap: 0 };
|
|
575
|
-
case JustifyContent.FlexEnd:
|
|
576
|
-
case JustifyContent.End:
|
|
577
|
-
case JustifyContent.Right:
|
|
578
|
-
return { offset: clamped, gap: 0 };
|
|
579
|
-
case JustifyContent.SpaceBetween:
|
|
580
|
-
if (itemCount === 1) {
|
|
581
|
-
return { offset: 0, gap: 0 };
|
|
582
|
-
}
|
|
583
|
-
return { offset: 0, gap: clamped / (itemCount - 1) };
|
|
584
|
-
case JustifyContent.SpaceAround: {
|
|
585
|
-
const gap = clamped / itemCount;
|
|
586
|
-
return { offset: gap / 2, gap };
|
|
587
|
-
}
|
|
588
|
-
case JustifyContent.SpaceEvenly: {
|
|
589
|
-
const gap = clamped / (itemCount + 1);
|
|
590
|
-
return { offset: gap, gap };
|
|
591
|
-
}
|
|
592
|
-
default:
|
|
593
|
-
return { offset: 0, gap: 0 };
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
function isRowDirection(direction) {
|
|
597
|
-
return direction === "row" || direction === "row-reverse";
|
|
598
|
-
}
|
|
599
|
-
function isAutoMainSize(value) {
|
|
600
|
-
if (value === undefined) {
|
|
601
|
-
return true;
|
|
602
|
-
}
|
|
603
|
-
if (typeof value === "number") {
|
|
604
|
-
return false;
|
|
605
|
-
}
|
|
606
|
-
if (value === "auto") {
|
|
607
|
-
return true;
|
|
608
|
-
}
|
|
609
|
-
return isAutoLength(value);
|
|
610
|
-
}
|
|
611
|
-
function computePreferredInlineWidth(node) {
|
|
612
|
-
let maxWidth = 0;
|
|
613
|
-
node.walk((desc) => {
|
|
614
|
-
if (desc.inlineRuns && desc.inlineRuns.length > 0) {
|
|
615
|
-
const localMax = desc.inlineRuns.reduce((max, run) => Math.max(max, run.lineWidth ?? run.width), 0);
|
|
616
|
-
if (localMax > maxWidth) {
|
|
617
|
-
maxWidth = localMax;
|
|
618
|
-
}
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
if (!desc.lineBoxes || desc.lineBoxes.length === 0) {
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
for (const line of desc.lineBoxes) {
|
|
625
|
-
if (!line) {
|
|
626
|
-
continue;
|
|
627
|
-
}
|
|
628
|
-
const width = typeof line.width === "number" ? line.width : 0;
|
|
629
|
-
if (width > maxWidth) {
|
|
630
|
-
maxWidth = width;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
});
|
|
634
|
-
return maxWidth > 0 ? maxWidth : undefined;
|
|
635
|
-
}
|
|
636
|
-
function offsetLayoutSubtree(node, deltaX, deltaY) {
|
|
637
|
-
if (deltaX === 0 && deltaY === 0) {
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
node.walk((desc) => {
|
|
641
|
-
if (desc !== node) {
|
|
642
|
-
desc.box.x += deltaX;
|
|
643
|
-
desc.box.y += deltaY;
|
|
644
|
-
}
|
|
645
|
-
desc.box.baseline += deltaY;
|
|
646
|
-
// Update inline runs if they exist
|
|
647
|
-
if (desc.inlineRuns && desc.inlineRuns.length > 0) {
|
|
648
|
-
for (const run of desc.inlineRuns) {
|
|
649
|
-
run.startX += deltaX;
|
|
650
|
-
run.baseline += deltaY;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
}
|
|
@@ -161,7 +161,6 @@ export class GridLayoutStrategy {
|
|
|
161
161
|
node.box.marginBoxWidth = node.box.borderBoxWidth + horizontalMargin(node, resolvedContentWidth, cb.height);
|
|
162
162
|
const columnOffsets = calculateTrackOffsets(columnWidths, columnGap);
|
|
163
163
|
const rows = [];
|
|
164
|
-
let currentRowTop = contentOriginY;
|
|
165
164
|
let currentRowHeight = 0;
|
|
166
165
|
let columnIndex = 0;
|
|
167
166
|
let currentRowItems = [];
|
|
@@ -174,7 +173,6 @@ export class GridLayoutStrategy {
|
|
|
174
173
|
if (columnIndex + span > columnWidths.length) {
|
|
175
174
|
if (currentRowItems.length > 0) {
|
|
176
175
|
rows.push({ items: currentRowItems, height: currentRowHeight });
|
|
177
|
-
currentRowTop += currentRowHeight + rowGap;
|
|
178
176
|
currentRowHeight = 0;
|
|
179
177
|
currentRowItems = [];
|
|
180
178
|
}
|
|
@@ -203,7 +201,6 @@ export class GridLayoutStrategy {
|
|
|
203
201
|
columnIndex += span;
|
|
204
202
|
if (columnIndex >= columnWidths.length) {
|
|
205
203
|
rows.push({ items: currentRowItems, height: currentRowHeight });
|
|
206
|
-
currentRowTop += currentRowHeight + rowGap;
|
|
207
204
|
currentRowHeight = 0;
|
|
208
205
|
columnIndex = 0;
|
|
209
206
|
currentRowItems = [];
|
|
@@ -3,7 +3,6 @@ import { LayoutNode } from "../../dom/node.js";
|
|
|
3
3
|
import { log } from "../../logging/debug.js";
|
|
4
4
|
import { resolveLength } from "../../css/length.js";
|
|
5
5
|
import { adjustForBoxSizing, containingBlock, horizontalNonContent, resolveWidthBlock, verticalNonContent } from "../utils/node-math.js";
|
|
6
|
-
import { layoutTableCell } from "../table/cell_layout.js";
|
|
7
6
|
import { auditTableCell, debugTableCell } from "../table/diagnostics.js";
|
|
8
7
|
export class TableLayoutStrategy {
|
|
9
8
|
constructor() {
|
|
@@ -42,22 +41,8 @@ export class TableLayoutStrategy {
|
|
|
42
41
|
if (!cell || !this.isOriginCell(cell, r, c))
|
|
43
42
|
continue;
|
|
44
43
|
const row = cell.parent;
|
|
45
|
-
// Set default border width and color for table cells if not set
|
|
46
|
-
const isTableCell = cell.tagName === 'td' || cell.tagName === 'th';
|
|
47
|
-
if (isTableCell) {
|
|
48
|
-
if (cell.style.borderTop === undefined)
|
|
49
|
-
cell.style.borderTop = collapsedBorders ? 0 : 1;
|
|
50
|
-
if (cell.style.borderRight === undefined)
|
|
51
|
-
cell.style.borderRight = collapsedBorders ? 0 : 1;
|
|
52
|
-
if (cell.style.borderBottom === undefined)
|
|
53
|
-
cell.style.borderBottom = collapsedBorders ? 0 : 1;
|
|
54
|
-
if (cell.style.borderLeft === undefined)
|
|
55
|
-
cell.style.borderLeft = collapsedBorders ? 0 : 1;
|
|
56
|
-
if (cell.style.borderColor === undefined)
|
|
57
|
-
cell.style.borderColor = '#000';
|
|
58
|
-
}
|
|
59
44
|
// Inherit from row/table if still not set
|
|
60
|
-
if (cell.style.borderTop === undefined) {
|
|
45
|
+
if (cell.style.borderTop === undefined || cell.style.borderTop === 0) {
|
|
61
46
|
if (row && row.style.borderTop !== undefined && row.style.borderTop !== 0) {
|
|
62
47
|
cell.style.borderTop = row.style.borderTop;
|
|
63
48
|
}
|
|
@@ -65,7 +50,7 @@ export class TableLayoutStrategy {
|
|
|
65
50
|
cell.style.borderTop = node.style.borderTop;
|
|
66
51
|
}
|
|
67
52
|
}
|
|
68
|
-
if (cell.style.borderRight === undefined) {
|
|
53
|
+
if (cell.style.borderRight === undefined || cell.style.borderRight === 0) {
|
|
69
54
|
if (row && row.style.borderRight !== undefined && row.style.borderRight !== 0) {
|
|
70
55
|
cell.style.borderRight = row.style.borderRight;
|
|
71
56
|
}
|
|
@@ -73,7 +58,7 @@ export class TableLayoutStrategy {
|
|
|
73
58
|
cell.style.borderRight = node.style.borderRight;
|
|
74
59
|
}
|
|
75
60
|
}
|
|
76
|
-
if (cell.style.borderBottom === undefined) {
|
|
61
|
+
if (cell.style.borderBottom === undefined || cell.style.borderBottom === 0) {
|
|
77
62
|
if (row && row.style.borderBottom !== undefined && row.style.borderBottom !== 0) {
|
|
78
63
|
cell.style.borderBottom = row.style.borderBottom;
|
|
79
64
|
}
|
|
@@ -81,7 +66,7 @@ export class TableLayoutStrategy {
|
|
|
81
66
|
cell.style.borderBottom = node.style.borderBottom;
|
|
82
67
|
}
|
|
83
68
|
}
|
|
84
|
-
if (cell.style.borderLeft === undefined) {
|
|
69
|
+
if (cell.style.borderLeft === undefined || cell.style.borderLeft === 0) {
|
|
85
70
|
if (row && row.style.borderLeft !== undefined && row.style.borderLeft !== 0) {
|
|
86
71
|
cell.style.borderLeft = row.style.borderLeft;
|
|
87
72
|
}
|
|
@@ -243,13 +228,20 @@ export class TableLayoutStrategy {
|
|
|
243
228
|
cell.box.y = 0;
|
|
244
229
|
cell.box.contentWidth = cellAvailableWidth;
|
|
245
230
|
debugTableCell(cell);
|
|
246
|
-
|
|
231
|
+
// Lay the cell out through the normal engine (BlockLayoutStrategy handles table-cell):
|
|
232
|
+
// this runs a real inline formatting context so mixed inline content (text + <b> + <a>)
|
|
233
|
+
// wraps and is positioned, and replaced content (<img>) gets sized by ImageLayoutStrategy.
|
|
234
|
+
// The cell resolves its content width from its containing block (the row), so temporarily
|
|
235
|
+
// set the row's content width to the cell's spanned column width while laying it out.
|
|
236
|
+
const row = cell.parent;
|
|
237
|
+
const savedRowWidth = row?.box.contentWidth;
|
|
238
|
+
if (row)
|
|
239
|
+
row.box.contentWidth = spannedWidth;
|
|
240
|
+
context.layoutChild(cell);
|
|
241
|
+
if (row && savedRowWidth !== undefined)
|
|
242
|
+
row.box.contentWidth = savedRowWidth;
|
|
247
243
|
if (cell.textContent?.includes("Row 3, Cell 3"))
|
|
248
244
|
auditTableCell(cell);
|
|
249
|
-
for (const child of cell.children) {
|
|
250
|
-
child.box.x = (child.box.x ?? 0) + boxMetrics.paddingLeft;
|
|
251
|
-
child.box.y = (child.box.y ?? 0) + boxMetrics.paddingTop;
|
|
252
|
-
}
|
|
253
245
|
if (cell.style.textAlign || cell.style.verticalAlign) {
|
|
254
246
|
for (const child of cell.children) {
|
|
255
247
|
if (cell.style.textAlign)
|
|
@@ -302,21 +294,31 @@ export class TableLayoutStrategy {
|
|
|
302
294
|
// The cell's border box starts at colOffsets[c], NOT at the content position
|
|
303
295
|
const borderBoxX = node.box.x + colOffsets[c];
|
|
304
296
|
const borderBoxY = node.box.y + rowOffsets[r];
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
const deltaX =
|
|
310
|
-
const deltaY =
|
|
297
|
+
// The cell was laid out by BlockLayoutStrategy with its border-box origin at (0,0),
|
|
298
|
+
// which already offsets its content by the cell's own border + padding. So the shift
|
|
299
|
+
// applied to descendants is the border-box delta (NOT the content delta), otherwise
|
|
300
|
+
// the cell's padding/border would be counted twice.
|
|
301
|
+
const deltaX = borderBoxX - cell.box.x;
|
|
302
|
+
const deltaY = borderBoxY - cell.box.y;
|
|
311
303
|
// Set cell position to border box position (not content position)
|
|
312
304
|
cell.box.x = borderBoxX;
|
|
313
305
|
cell.box.y = borderBoxY;
|
|
314
|
-
// Apply the
|
|
306
|
+
// Apply the border-box offset to all of the cell's descendants, plus any vertical-align
|
|
307
|
+
// offset. Inline runs produced by the inline formatter carry their own baked-in
|
|
308
|
+
// coordinates (startX/baseline), so they must be shifted too — otherwise wrapped cell
|
|
309
|
+
// text stays at its (0,0) layout origin while images (positioned via box) move correctly.
|
|
310
|
+
const shiftY = deltaY + alignOffsetY;
|
|
315
311
|
cell.walk((descendant) => {
|
|
316
312
|
descendant.box.x += deltaX;
|
|
317
|
-
descendant.box.y +=
|
|
313
|
+
descendant.box.y += shiftY;
|
|
318
314
|
if (descendant.box.baseline !== undefined) {
|
|
319
|
-
descendant.box.baseline +=
|
|
315
|
+
descendant.box.baseline += shiftY;
|
|
316
|
+
}
|
|
317
|
+
if (descendant.inlineRuns) {
|
|
318
|
+
for (const run of descendant.inlineRuns) {
|
|
319
|
+
run.startX += deltaX;
|
|
320
|
+
run.baseline += shiftY;
|
|
321
|
+
}
|
|
320
322
|
}
|
|
321
323
|
}, false);
|
|
322
324
|
// Set border box dimensions
|
|
@@ -434,55 +436,80 @@ export class TableLayoutStrategy {
|
|
|
434
436
|
const numCols = grid[0]?.length || 0;
|
|
435
437
|
if (numCols === 0)
|
|
436
438
|
return [];
|
|
437
|
-
|
|
439
|
+
// Per-column min-content (longest unbreakable word) and max-content (preferred,
|
|
440
|
+
// whole text on one line) widths, following the CSS auto table layout model.
|
|
441
|
+
const minWidths = new Array(numCols).fill(0);
|
|
442
|
+
const maxWidths = new Array(numCols).fill(0);
|
|
438
443
|
for (let r = 0; r < grid.length; r++) {
|
|
439
444
|
for (let c = 0; c < numCols; c++) {
|
|
440
445
|
const cell = grid[r][c];
|
|
441
446
|
if (!cell || !this.isOriginCell(cell, r, c))
|
|
442
447
|
continue;
|
|
443
|
-
let
|
|
444
|
-
|
|
445
|
-
maxIntrinsicWidth = cell.intrinsicInlineSize;
|
|
446
|
-
}
|
|
448
|
+
let cellMinContent = cell.minIntrinsicInlineSize ?? cell.intrinsicInlineSize ?? 0;
|
|
449
|
+
let cellMaxContent = cell.intrinsicInlineSize ?? 0;
|
|
447
450
|
cell.walk((node) => {
|
|
448
|
-
|
|
449
|
-
|
|
451
|
+
const nodeMax = node.intrinsicInlineSize;
|
|
452
|
+
if (nodeMax !== undefined) {
|
|
453
|
+
cellMaxContent = Math.max(cellMaxContent, nodeMax);
|
|
454
|
+
// Images and other replaced content do not record a min-content width;
|
|
455
|
+
// they cannot wrap, so their max-content doubles as the min-content.
|
|
456
|
+
const nodeMin = node.minIntrinsicInlineSize ?? nodeMax;
|
|
457
|
+
cellMinContent = Math.max(cellMinContent, nodeMin);
|
|
450
458
|
}
|
|
451
459
|
});
|
|
452
460
|
const horizontalExtras = horizontalNonContent(cell, tableWidth);
|
|
453
|
-
const
|
|
461
|
+
const cellMin = cellMinContent + horizontalExtras;
|
|
462
|
+
const cellMax = cellMaxContent + horizontalExtras;
|
|
454
463
|
const colSpan = Math.min(this.cellColSpan(cell), numCols - c);
|
|
455
464
|
if (colSpan === 1) {
|
|
456
|
-
|
|
465
|
+
minWidths[c] = Math.max(minWidths[c], cellMin);
|
|
466
|
+
maxWidths[c] = Math.max(maxWidths[c], cellMax);
|
|
457
467
|
}
|
|
458
468
|
else {
|
|
459
|
-
const
|
|
469
|
+
const minShare = cellMin / colSpan;
|
|
470
|
+
const maxShare = cellMax / colSpan;
|
|
460
471
|
for (let offset = 0; offset < colSpan; offset++) {
|
|
461
|
-
|
|
472
|
+
minWidths[c + offset] = Math.max(minWidths[c + offset], minShare);
|
|
473
|
+
maxWidths[c + offset] = Math.max(maxWidths[c + offset], maxShare);
|
|
462
474
|
}
|
|
463
475
|
}
|
|
464
476
|
}
|
|
465
477
|
}
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
478
|
+
const totalMin = minWidths.reduce((a, b) => a + b, 0);
|
|
479
|
+
const totalMax = maxWidths.reduce((a, b) => a + b, 0);
|
|
480
|
+
// Preferred widths fit: lay out at max-content and distribute any slack.
|
|
481
|
+
if (totalMax <= tableWidth) {
|
|
482
|
+
if (totalMax <= 0) {
|
|
483
|
+
return new Array(numCols).fill(tableWidth / numCols);
|
|
484
|
+
}
|
|
485
|
+
const remaining = tableWidth - totalMax;
|
|
486
|
+
const weights = maxWidths.map((w) => (w > 0 ? w : 0));
|
|
470
487
|
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
471
488
|
if (totalWeight > 0) {
|
|
472
|
-
return
|
|
473
|
-
return minWidth + remainingWidth * (weights[i] / totalWeight);
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
return new Array(numCols).fill(tableWidth / numCols);
|
|
489
|
+
return maxWidths.map((w, i) => w + remaining * (weights[i] / totalWeight));
|
|
478
490
|
}
|
|
491
|
+
return new Array(numCols).fill(tableWidth / numCols);
|
|
479
492
|
}
|
|
480
|
-
|
|
481
|
-
|
|
493
|
+
// Preferred widths overflow but min-content fits: start at min-content and grow each
|
|
494
|
+
// column toward its preferred width by a shared fraction of the leftover space. This lets
|
|
495
|
+
// wide text columns shrink and wrap while fixed/replaced columns keep their natural width.
|
|
496
|
+
if (totalMin <= tableWidth) {
|
|
497
|
+
const growthRoom = totalMax - totalMin;
|
|
498
|
+
const available = tableWidth - totalMin;
|
|
499
|
+
if (growthRoom <= 0) {
|
|
500
|
+
return minWidths;
|
|
501
|
+
}
|
|
502
|
+
const fraction = available / growthRoom;
|
|
503
|
+
return minWidths.map((min, i) => min + (maxWidths[i] - min) * fraction);
|
|
482
504
|
}
|
|
483
|
-
|
|
484
|
-
|
|
505
|
+
// Even min-content overflows (e.g. an unbreakable URL wider than the page). Scale the columns
|
|
506
|
+
// down proportionally so the table still fits the page; the line-breaker's emergency break then
|
|
507
|
+
// wraps the over-long token inside its column instead of letting it run off the page.
|
|
508
|
+
if (totalMin > 0) {
|
|
509
|
+
const scale = tableWidth / totalMin;
|
|
510
|
+
return minWidths.map((w) => w * scale);
|
|
485
511
|
}
|
|
512
|
+
return new Array(numCols).fill(tableWidth / numCols);
|
|
486
513
|
}
|
|
487
514
|
isOriginCell(cell, row, col) {
|
|
488
515
|
const origin = cell.tableCellOrigin;
|
|
@@ -47,11 +47,13 @@ export function assignIntrinsicTextMetrics(root, fontEmbedder) {
|
|
|
47
47
|
const trimmed = node.textContent;
|
|
48
48
|
if (trimmed.length === 0) {
|
|
49
49
|
node.intrinsicInlineSize = 0;
|
|
50
|
+
node.minIntrinsicInlineSize = 0;
|
|
50
51
|
node.intrinsicBlockSize = resolvedLineHeight(node.style);
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
|
-
const { inlineSize, blockSize } = measureText(trimmed, node.style, fontEmbedder);
|
|
54
|
+
const { inlineSize, minInlineSize, blockSize } = measureText(trimmed, node.style, fontEmbedder);
|
|
54
55
|
node.intrinsicInlineSize = inlineSize;
|
|
56
|
+
node.minIntrinsicInlineSize = minInlineSize;
|
|
55
57
|
node.intrinsicBlockSize = blockSize;
|
|
56
58
|
});
|
|
57
59
|
}
|
|
@@ -59,21 +61,27 @@ function measureText(text, style, fontEmbedder) {
|
|
|
59
61
|
const effectiveText = applyTextTransform(text, style.textTransform);
|
|
60
62
|
const lines = effectiveText.split(/\r?\n/);
|
|
61
63
|
let maxLineWidth = 0;
|
|
64
|
+
// min-content width = widest unbreakable word (longest run with no whitespace).
|
|
65
|
+
// Columns may shrink down to this without forcing a single word to overflow.
|
|
66
|
+
let maxWordWidth = 0;
|
|
62
67
|
const fontWeight = typeof style.fontWeight === "number" ? style.fontWeight : 400;
|
|
63
68
|
const fontStyle = style.fontStyle ?? "normal";
|
|
64
69
|
const fontMetrics = fontEmbedder?.getMetrics(style.fontFamily ?? "", fontWeight, fontStyle);
|
|
70
|
+
const measureSegment = (segment) => {
|
|
71
|
+
const glyphWidth = measureTextWithGlyphs(segment, style, fontMetrics ?? null);
|
|
72
|
+
return glyphWidth !== null ? glyphWidth : estimateLineWidth(segment, style);
|
|
73
|
+
};
|
|
65
74
|
for (const line of lines) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
maxLineWidth = Math.max(maxLineWidth, estimateLineWidth(line, style));
|
|
75
|
+
maxLineWidth = Math.max(maxLineWidth, measureSegment(line));
|
|
76
|
+
for (const word of line.split(/\s+/)) {
|
|
77
|
+
if (word.length === 0)
|
|
78
|
+
continue;
|
|
79
|
+
maxWordWidth = Math.max(maxWordWidth, measureSegment(word));
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
const lineHeight = resolvedLineHeight(style);
|
|
75
83
|
const blockSize = Math.max(lineHeight, lines.length * lineHeight);
|
|
76
|
-
return { inlineSize: maxLineWidth, blockSize };
|
|
84
|
+
return { inlineSize: maxLineWidth, minInlineSize: maxWordWidth, blockSize };
|
|
77
85
|
}
|
|
78
86
|
export function estimateLineWidth(line, style) {
|
|
79
87
|
if (!line) {
|
|
@@ -90,7 +90,7 @@ export class FontEmbedder {
|
|
|
90
90
|
const fullFontData = new Uint8Array(face.data);
|
|
91
91
|
const resourceName = `F${this.embeddedFonts.size + 1}`;
|
|
92
92
|
let realizedRef;
|
|
93
|
-
const
|
|
93
|
+
const realizeFont = this.realizeFont.bind(this);
|
|
94
94
|
const embedded = {
|
|
95
95
|
resourceName,
|
|
96
96
|
baseFont: face.name,
|
|
@@ -98,14 +98,14 @@ export class FontEmbedder {
|
|
|
98
98
|
subset: fullFontData,
|
|
99
99
|
get ref() {
|
|
100
100
|
if (!realizedRef) {
|
|
101
|
-
realizedRef =
|
|
101
|
+
realizedRef = realizeFont(face, metrics);
|
|
102
102
|
}
|
|
103
103
|
return realizedRef;
|
|
104
104
|
}
|
|
105
105
|
};
|
|
106
106
|
return embedded;
|
|
107
107
|
}
|
|
108
|
-
realizeFont(face, metrics
|
|
108
|
+
realizeFont(face, metrics) {
|
|
109
109
|
const unitsPerEm = metrics.metrics.unitsPerEm;
|
|
110
110
|
const scaleTo1000 = (v) => Math.round((v / unitsPerEm) * 1000);
|
|
111
111
|
let fontBBox;
|