buninu-browser 0.2.2 → 0.2.4
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/lib/audio/playback-buffer.js +27 -0
- package/lib/audio/pulse-client.js +18 -8
- package/lib/cdp-server.js +53 -14
- package/lib/happy-dom/document-base.js +33 -0
- package/lib/happy-dom/parser.js +20 -6
- package/lib/happy-dom/selector-matcher.js +2 -2
- package/lib/happy-dom/selector-pseudo-adapter.js +33 -0
- package/lib/happy-dom/svg-attribute-adapter.js +50 -0
- package/lib/headless-shell.js +12 -6
- package/lib/input/hit-test.js +67 -19
- package/lib/layout/text-layout.js +289 -131
- package/lib/media/byte-pipe.js +2 -2
- package/lib/media/position-audio.js +10 -1
- package/lib/media/source-snapshots.js +126 -0
- package/lib/media/video-keyframe.js +42 -0
- package/lib/media/webm-stream.js +106 -0
- package/lib/network/network-service.js +268 -120
- package/lib/paint/display-list.js +22 -27
- package/lib/paint/frame-cache.js +25 -0
- package/lib/paint/frame.js +42 -13
- package/lib/paint/paragraph-text.js +24 -0
- package/lib/paint/raster-readback.js +41 -0
- package/lib/paint/rounded-rect.js +82 -0
- package/lib/paint/shared-frame-pool.js +105 -0
- package/lib/render-tree/index.js +40 -6
- package/lib/render-tree/svg.js +15 -7
- package/lib/renderer/bindings.js +244 -82
- package/lib/renderer/broadcast-channel.js +76 -0
- package/lib/renderer/canvas-2d.js +257 -14
- package/lib/renderer/canvas-text.js +120 -0
- package/lib/renderer/compositor.js +27 -10
- package/lib/renderer/custom-element-reactions.js +42 -0
- package/lib/renderer/font-loading.js +227 -0
- package/lib/renderer/history.js +42 -0
- package/lib/renderer/host.js +36 -5
- package/lib/renderer/image-loading.js +27 -0
- package/lib/renderer/image-resources.js +65 -0
- package/lib/renderer/interfaces.js +28 -0
- package/lib/renderer/keyboard-input.js +12 -0
- package/lib/renderer/layout-observers.js +236 -0
- package/lib/renderer/main.js +33 -5
- package/lib/renderer/media-playback-events.js +13 -0
- package/lib/renderer/media-source.js +126 -55
- package/lib/renderer/page-realm.js +278 -147
- package/lib/renderer/page-renderer.js +840 -301
- package/lib/renderer/piano-input.js +22 -0
- package/lib/renderer/pointer-move.js +29 -0
- package/lib/renderer/prepared-script-cache.js +50 -0
- package/lib/renderer/rendered-text.js +108 -0
- package/lib/renderer/rewrite-worker.js +4 -3
- package/lib/renderer/script-rewrite.js +13 -2
- package/lib/renderer/scroll-into-view.js +28 -0
- package/lib/renderer/slot-assignment.js +60 -0
- package/lib/renderer/video-presentation.js +145 -0
- package/lib/renderer/workers.js +2 -2
- package/lib/style/computed-style.js +632 -108
- package/lib/style/selector-traits.js +49 -5
- package/package.json +1 -1
- package/test/broadcast-channel.test.js +26 -0
- package/test/canvas-2d.test.js +83 -2
- package/test/canvas-paint.test.js +9 -0
- package/test/canvas-text.test.js +41 -0
- package/test/cdp-stream.test.js +56 -0
- package/test/compositor.test.js +38 -0
- package/test/computed-style-binding.test.js +151 -0
- package/test/computed-style.test.js +279 -1
- package/test/display-list.test.js +26 -0
- package/test/document-base.test.js +87 -0
- package/test/dom-platform.test.js +461 -0
- package/test/download.test.js +8 -0
- package/test/fetch-lifecycle.test.js +161 -0
- package/test/fixtures/canvas-paint.js +117 -0
- package/test/fixtures/canvas-text.js +42 -0
- package/test/fixtures/image-lifetime.js +90 -0
- package/test/fixtures/media-seek-snapshot.js +77 -0
- package/test/fixtures/mse-audio-chirp.webm +0 -0
- package/test/fixtures/mse-switch-large.mp4 +0 -0
- package/test/fixtures/mse-switch-small.mp4 +0 -0
- package/test/fixtures/renderer-memory.js +110 -0
- package/test/fixtures/summary-style.js +28 -0
- package/test/font-loading.test.js +97 -0
- package/test/grid.test.js +58 -0
- package/test/headless-shell.test.js +22 -0
- package/test/hit-test.test.js +21 -1
- package/test/image-lifetime.test.js +12 -0
- package/test/image-resources.test.js +94 -0
- package/test/local-media.test.js +84 -0
- package/test/media-playback-events.test.js +22 -0
- package/test/media-seek-snapshot.test.js +11 -0
- package/test/media-source-config.test.js +98 -0
- package/test/media-source-webm-accounting.test.js +98 -0
- package/test/media-source-webm.test.js +108 -0
- package/test/navigation.test.js +1 -0
- package/test/network-service.test.js +388 -0
- package/test/page-scripts.test.js +160 -2
- package/test/paint-frame.test.js +103 -0
- package/test/paint-paragraph-cache.test.js +87 -0
- package/test/piano-input.test.js +44 -0
- package/test/playback-buffer.test.js +31 -0
- package/test/position-audio.test.js +24 -0
- package/test/positioning.test.js +22 -0
- package/test/prepared-script-cache.test.js +45 -0
- package/test/pulse-client.test.js +27 -5
- package/test/raster-readback.test.js +40 -0
- package/test/render-tree.test.js +28 -0
- package/test/renderer-memory.test.js +11 -0
- package/test/selector-traits.test.js +24 -0
- package/test/shared-frame-pool.test.js +63 -0
- package/test/style-sheets.test.js +51 -0
- package/test/svg.test.js +29 -0
- package/test/text-layout.test.js +168 -0
- package/test/video-keyframe.test.js +61 -0
- package/test/video-presentation.test.js +136 -0
- package/tools/browser-lab.js +78 -0
- package/tools/browser-soak.js +75 -0
- package/tools/network-reference.js +49 -0
- package/tools/process-memory.js +38 -0
- package/tools/script-benchmark.js +25 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Box-tree layout: block formatting contexts with margin collapsing, inline
|
|
3
3
|
* formatting contexts with line boxes, atomic inlines, and flex layout
|
|
4
|
-
* (CSS Flexbox §9: flexible length resolution, wrapping,
|
|
5
|
-
*
|
|
6
|
-
* layout (CSS Grid 2 §8 placement and §12 track sizing,
|
|
7
|
-
*
|
|
4
|
+
* (CSS Flexbox §9: row/column flexible length resolution, row wrapping,
|
|
5
|
+
* alignment), and grid
|
|
6
|
+
* layout (CSS Grid 2 §8 placement and §12 track sizing, including named areas
|
|
7
|
+
* and row/column auto-flow). Relative, absolute and fixed positioning
|
|
8
8
|
* place boxes after flow layout; positioned boxes get their own paint layer.
|
|
9
9
|
*
|
|
10
10
|
* The block/inline structure follows concepts from Dropflow
|
|
@@ -23,6 +23,14 @@ const ZERO_STRUT = Object.freeze({ positive: 0, negative: 0 });
|
|
|
23
23
|
const DEFAULT_ASCENT = 0.93;
|
|
24
24
|
const DEFAULT_CONTENT_HEIGHT = 1.17;
|
|
25
25
|
|
|
26
|
+
// CSS 2 §8.4: all four percentage paddings use the containing block's width.
|
|
27
|
+
// Cyclic percentages contribute zero during intrinsic sizing.
|
|
28
|
+
function paddingBox(style, width = 0) {
|
|
29
|
+
const padding = style?.padding ?? ZERO_BOX;
|
|
30
|
+
return padding.every(Number.isFinite) ? padding
|
|
31
|
+
: padding.map(value => Math.max(0, numberOrZero(resolveLength(value, width))));
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
const ROOT_LAYER = Object.freeze({ z: 0, order: 0, context: true, parent: null });
|
|
27
35
|
|
|
28
36
|
export function layoutText(tree, options = {}) {
|
|
@@ -142,7 +150,7 @@ let containingBlocks = null;
|
|
|
142
150
|
let outOfFlowBoxes = null;
|
|
143
151
|
|
|
144
152
|
// Text nodes carry their parent's style, so only element boxes can be positioned.
|
|
145
|
-
const UNPOSITIONABLE = new Set(["root", "text", "line-break"]);
|
|
153
|
+
const UNPOSITIONABLE = new Set(["root", "text", "line-break", "contents"]);
|
|
146
154
|
|
|
147
155
|
function isPositioned(node) {
|
|
148
156
|
return !UNPOSITIONABLE.has(node.type) && ["relative", "absolute", "fixed", "sticky"].includes(node.style?.position);
|
|
@@ -209,7 +217,10 @@ function blockChildren(node, flags) {
|
|
|
209
217
|
};
|
|
210
218
|
|
|
211
219
|
function walk(child, childFlags) {
|
|
212
|
-
if (
|
|
220
|
+
if (child.type === "contents") {
|
|
221
|
+
const flags = inheritedFlags(child, childFlags);
|
|
222
|
+
for (const grandchild of child.children) walk(grandchild, flags);
|
|
223
|
+
} else if (isOutOfFlow(child)) {
|
|
213
224
|
const box = outOfFlowBox(child, childFlags);
|
|
214
225
|
// Between blocks the placeholder records the static position; inline
|
|
215
226
|
// placeholders fall back to the containing block's origin.
|
|
@@ -263,10 +274,10 @@ function flexItems(node, flags) {
|
|
|
263
274
|
}
|
|
264
275
|
text = [];
|
|
265
276
|
};
|
|
266
|
-
for (const child of node
|
|
277
|
+
for (const child of boxChildren(node)) {
|
|
267
278
|
if (child.type === "line-break") continue;
|
|
268
279
|
if (child.type === "text") {
|
|
269
|
-
if (child.text.trim()) text.push(textItem(child, flags));
|
|
280
|
+
if (child.text.trim()) text.push(textItem(child, inheritedFlags(child, flags)));
|
|
270
281
|
continue;
|
|
271
282
|
}
|
|
272
283
|
if (isOutOfFlow(child)) {
|
|
@@ -280,6 +291,14 @@ function flexItems(node, flags) {
|
|
|
280
291
|
return items;
|
|
281
292
|
}
|
|
282
293
|
|
|
294
|
+
/** display:contents contributes its descendants directly to the box tree. */
|
|
295
|
+
function* boxChildren(node) {
|
|
296
|
+
for (const child of node.children) {
|
|
297
|
+
if (child.type === "contents") yield* boxChildren(child);
|
|
298
|
+
else yield child;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
283
302
|
function textItem(node, flags) {
|
|
284
303
|
return { kind: "text", node, style: node.style ?? null, text: node.text, preserve: flags.preserve, wrap: flags.wrap };
|
|
285
304
|
}
|
|
@@ -325,7 +344,7 @@ function layoutBlock(ctx, box, x, width, y, absorbTop = false, forcedHeight = nu
|
|
|
325
344
|
const style = box.style;
|
|
326
345
|
// Its containing block, for percentages in relative offsets.
|
|
327
346
|
const containing = ctx.containing;
|
|
328
|
-
const key = blockCacheKey(width, absorbTop, forcedHeight,
|
|
347
|
+
const key = blockCacheKey(width, absorbTop, forcedHeight, containing);
|
|
329
348
|
const cached = cachedBlockResult(ctx, box, key, y, x);
|
|
330
349
|
if (cached) return cached;
|
|
331
350
|
const boxStart = ctx.boxes.length;
|
|
@@ -356,9 +375,11 @@ function layoutBlock(ctx, box, x, width, y, absorbTop = false, forcedHeight = nu
|
|
|
356
375
|
dx = offset.x;
|
|
357
376
|
dy = offset.y;
|
|
358
377
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
378
|
+
// Individual translate precedes the transform list; its offset is in the
|
|
379
|
+
// parent's coordinates and is not multiplied by the list's own scale.
|
|
380
|
+
for (const translation of [style?.translate, style?.transform]) if (translation) {
|
|
381
|
+
dx += numberOrZero(resolveLength(translation.x, record.width));
|
|
382
|
+
dy += numberOrZero(resolveLength(translation.y, record.height));
|
|
362
383
|
}
|
|
363
384
|
for (let index = boxStart; index < ctx.boxes.length; index++) ctx.boxes[index].layer ??= ctx.layer;
|
|
364
385
|
for (let index = fragmentStart; index < ctx.fragments.length; index++) ctx.fragments[index].layer ??= ctx.layer;
|
|
@@ -511,31 +532,37 @@ function layoutAbsolutes(ctx, box, record) {
|
|
|
511
532
|
for (const absolute of box.absolutes) {
|
|
512
533
|
const style = absolute.style;
|
|
513
534
|
const containing = style?.position === "fixed" ? { ...icb, height: icb.height || block.height } : block;
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
535
|
+
const previousContaining = ctx.containing;
|
|
536
|
+
ctx.containing = containing;
|
|
537
|
+
try {
|
|
538
|
+
const [top, right, bottom, left] = (style?.inset ?? ["auto", "auto", "auto", "auto"])
|
|
539
|
+
.map((value, index) => value === "auto" ? null : resolveLength(value, index % 2 ? containing.width : containing.height));
|
|
540
|
+
const margin = style?.margin ?? ZERO_BOX;
|
|
541
|
+
const [marginTop, marginRight, marginBottom, marginLeft] = margin.map(numberOrZero);
|
|
542
|
+
const specifiedWidth = numberOrNull(resolveLength(style?.width, containing.width));
|
|
543
|
+
let width;
|
|
544
|
+
if (specifiedWidth == null && left != null && right != null) {
|
|
545
|
+
width = Math.max(0, containing.width - left - right - marginLeft - marginRight);
|
|
546
|
+
} else {
|
|
547
|
+
width = resolveHorizontal(ctx, absolute, 0, containing.width - (left ?? 0) - (right ?? 0), true, containing.width).width;
|
|
548
|
+
}
|
|
549
|
+
const staticPosition = absolute.staticPosition ?? containing;
|
|
550
|
+
const x = left != null ? containing.x + left + marginLeft
|
|
551
|
+
: right != null ? containing.x + containing.width - right - marginRight - width
|
|
552
|
+
: staticPosition.x + marginLeft;
|
|
553
|
+
const specifiedHeight = numberOrNull(resolveLength(style?.height, containing.height));
|
|
554
|
+
const forcedHeight = specifiedHeight == null && top != null && bottom != null
|
|
555
|
+
? Math.max(0, containing.height - top - bottom - marginTop - marginBottom)
|
|
556
|
+
: null;
|
|
557
|
+
let height = forcedHeight;
|
|
558
|
+
if (height == null && top == null && bottom != null) height = dryLayout(ctx, absolute, width).height;
|
|
559
|
+
const y = top != null ? containing.y + top + marginTop
|
|
560
|
+
: bottom != null ? containing.y + containing.height - bottom - marginBottom - height
|
|
561
|
+
: staticPosition.y + marginTop;
|
|
562
|
+
layoutBlock(ctx, absolute, x, width, y, false, forcedHeight);
|
|
563
|
+
} finally {
|
|
564
|
+
ctx.containing = previousContaining;
|
|
524
565
|
}
|
|
525
|
-
const staticPosition = absolute.staticPosition ?? containing;
|
|
526
|
-
const x = left != null ? containing.x + left + marginLeft
|
|
527
|
-
: right != null ? containing.x + containing.width - right - marginRight - width
|
|
528
|
-
: staticPosition.x + marginLeft;
|
|
529
|
-
const specifiedHeight = numberOrNull(resolveLength(style?.height, containing.height));
|
|
530
|
-
const forcedHeight = specifiedHeight == null && top != null && bottom != null
|
|
531
|
-
? Math.max(0, containing.height - top - bottom - marginTop - marginBottom)
|
|
532
|
-
: null;
|
|
533
|
-
let height = forcedHeight;
|
|
534
|
-
if (height == null && top == null && bottom != null) height = dryLayout(ctx, absolute, width).height;
|
|
535
|
-
const y = top != null ? containing.y + top + marginTop
|
|
536
|
-
: bottom != null ? containing.y + containing.height - bottom - marginBottom - height
|
|
537
|
-
: staticPosition.y + marginTop;
|
|
538
|
-
layoutBlock(ctx, absolute, x, width, y, false, forcedHeight);
|
|
539
566
|
}
|
|
540
567
|
}
|
|
541
568
|
|
|
@@ -546,15 +573,23 @@ function layoutAbsolutes(ctx, box, record) {
|
|
|
546
573
|
* item by item because shiftOutput moves everything after a start index.
|
|
547
574
|
*/
|
|
548
575
|
function justifyColumn(ctx, items, justify = "normal", free) {
|
|
549
|
-
|
|
576
|
+
const autoMargins = items.reduce((sum, item) => sum + Number(item.autoTop) + Number(item.autoBottom), 0);
|
|
577
|
+
if (!free || !autoMargins && ["normal", "start", "flex-start", "stretch"].includes(justify)) return;
|
|
550
578
|
const count = items.length;
|
|
579
|
+
let autoOffset = 0;
|
|
551
580
|
const offset = (index) => {
|
|
581
|
+
if (autoMargins && free > 0) {
|
|
582
|
+
autoOffset += Number(items[index].autoTop) * free / autoMargins;
|
|
583
|
+
const result = autoOffset;
|
|
584
|
+
autoOffset += Number(items[index].autoBottom) * free / autoMargins;
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
552
587
|
switch (justify) {
|
|
553
588
|
case "center": return free / 2;
|
|
554
589
|
case "end": case "flex-end": return free;
|
|
555
|
-
case "space-between": return count > 1 ? free * index / (count - 1) : 0;
|
|
556
|
-
case "space-around": return free / count * (index + 0.5);
|
|
557
|
-
case "space-evenly": return free / (count + 1) * (index + 1);
|
|
590
|
+
case "space-between": return free > 0 && count > 1 ? free * index / (count - 1) : 0;
|
|
591
|
+
case "space-around": return free > 0 ? free / count * (index + 0.5) : free / 2;
|
|
592
|
+
case "space-evenly": return free > 0 ? free / (count + 1) * (index + 1) : free / 2;
|
|
558
593
|
default: return 0;
|
|
559
594
|
}
|
|
560
595
|
};
|
|
@@ -593,9 +628,9 @@ function shiftedFragment(fragment, dx, dy) {
|
|
|
593
628
|
}
|
|
594
629
|
|
|
595
630
|
function layoutBlockContent(ctx, box, x, width, y, absorbTop = false, forcedHeight = null) {
|
|
596
|
-
const
|
|
631
|
+
const padding = paddingBox(box.style, ctx.containing?.width ?? width);
|
|
632
|
+
const style = box.style && padding !== box.style.padding ? { ...box.style, padding } : box.style;
|
|
597
633
|
const border = borderWidths(style);
|
|
598
|
-
const padding = style?.padding ?? ZERO_BOX;
|
|
599
634
|
const record = { nodeId: box.node.id, x, y, width, height: 0, style };
|
|
600
635
|
ctx.boxes.push(record);
|
|
601
636
|
if (box.replaced) {
|
|
@@ -639,6 +674,8 @@ function layoutBlockContent(ctx, box, x, width, y, absorbTop = false, forcedHeig
|
|
|
639
674
|
const containingHeight = ctx.containing?.height;
|
|
640
675
|
const resolveHeight = (value) => numberOrNull(resolveLength(value, containingHeight ?? NaN));
|
|
641
676
|
const specifiedHeight = resolveHeight(style?.height);
|
|
677
|
+
const minHeight = resolveHeight(style?.minHeight);
|
|
678
|
+
const maxHeight = style?.maxHeight === "none" ? null : resolveHeight(style?.maxHeight);
|
|
642
679
|
const previousContaining = ctx.containing;
|
|
643
680
|
ctx.containing = {
|
|
644
681
|
width: contentWidth,
|
|
@@ -660,38 +697,11 @@ function layoutBlockContent(ctx, box, x, width, y, absorbTop = false, forcedHeig
|
|
|
660
697
|
contentBottom = result.bottom;
|
|
661
698
|
baseline = result.baseline;
|
|
662
699
|
} else if (box.flex === "column") {
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
columnItems.push({ boxStart: ctx.boxes.length, fragmentStart: ctx.fragments.length });
|
|
669
|
-
if (child.kind === "ifc") {
|
|
670
|
-
const result = layoutInline(ctx, child, contentX, contentWidth, cursor);
|
|
671
|
-
cursor = result.bottom;
|
|
672
|
-
baseline = result.baseline ?? baseline;
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
const alignment = ["auto", "normal", undefined].includes(child.style?.alignSelf)
|
|
676
|
-
? style?.alignItems ?? "normal"
|
|
677
|
-
: child.style.alignSelf;
|
|
678
|
-
const shrink = !["normal", "stretch"].includes(alignment);
|
|
679
|
-
const placed = resolveHorizontal(ctx, child, contentX, contentWidth, shrink);
|
|
680
|
-
// Auto margins on the cross axis (left, right) align the item instead (Flexbox §8.1).
|
|
681
|
-
const crossMargins = child.style?.margin ?? ZERO_BOX;
|
|
682
|
-
const autoMargins = crossMargins[1] === "auto" || crossMargins[3] === "auto";
|
|
683
|
-
let childX = placed.x;
|
|
684
|
-
if (shrink && !autoMargins) {
|
|
685
|
-
const free = contentWidth - placed.width - placed.marginLeft - placed.marginRight;
|
|
686
|
-
if (alignment === "center") childX = contentX + placed.marginLeft + free / 2;
|
|
687
|
-
else if (alignment === "end") childX = contentX + placed.marginLeft + free;
|
|
688
|
-
}
|
|
689
|
-
const margin = verticalMargins(child.style);
|
|
690
|
-
const result = layoutBlock(ctx, child, childX, placed.width, cursor + margin.top);
|
|
691
|
-
cursor += margin.top + result.height + margin.bottom;
|
|
692
|
-
baseline = result.baseline ?? baseline;
|
|
693
|
-
});
|
|
694
|
-
contentBottom = cursor;
|
|
700
|
+
const result = layoutFlexColumn(ctx, box, contentX, contentWidth, contentTop,
|
|
701
|
+
minHeight == null ? 0 : toContentHeight(minHeight), maxHeight == null ? Infinity : toContentHeight(maxHeight));
|
|
702
|
+
contentBottom = result.bottom;
|
|
703
|
+
baseline = result.baseline;
|
|
704
|
+
columnItems = result.items;
|
|
695
705
|
} else {
|
|
696
706
|
let cursor = contentTop;
|
|
697
707
|
let pending = null;
|
|
@@ -718,8 +728,13 @@ function layoutBlockContent(ctx, box, x, width, y, absorbTop = false, forcedHeig
|
|
|
718
728
|
// Tables with width: auto are sized to their contents (CSS 2 §17.5.2).
|
|
719
729
|
const placed = resolveHorizontal(ctx, child, contentX, contentWidth, Boolean(child.table));
|
|
720
730
|
const result = layoutBlock(ctx, child, placed.x, placed.width, childY, collapsesThroughTop(child));
|
|
721
|
-
|
|
722
|
-
|
|
731
|
+
if (result.height === 0 && selfCollapsing(child)) {
|
|
732
|
+
// Empty blocks do not separate adjoining margins (CSS 2 §8.3.1).
|
|
733
|
+
pending = combineStruts(combined, bottomStrut(child));
|
|
734
|
+
} else {
|
|
735
|
+
cursor = childY + result.height;
|
|
736
|
+
pending = bottomStrut(child);
|
|
737
|
+
}
|
|
723
738
|
baseline = result.baseline ?? baseline;
|
|
724
739
|
});
|
|
725
740
|
contentBottom = collapsesThroughBottom(box) ? cursor : cursor + strutValue(pending ?? ZERO_STRUT);
|
|
@@ -728,14 +743,15 @@ function layoutBlockContent(ctx, box, x, width, y, absorbTop = false, forcedHeig
|
|
|
728
743
|
ctx.containing = previousContaining;
|
|
729
744
|
let contentHeight = Math.max(0, contentBottom - contentTop);
|
|
730
745
|
if (specifiedHeight != null) contentHeight = toContentHeight(specifiedHeight);
|
|
731
|
-
const maxHeight = style?.maxHeight === "none" ? null : resolveHeight(style?.maxHeight);
|
|
732
746
|
if (maxHeight != null) contentHeight = Math.min(contentHeight, toContentHeight(maxHeight));
|
|
733
|
-
const minHeight = resolveHeight(style?.minHeight);
|
|
734
747
|
if (minHeight != null) contentHeight = Math.max(contentHeight, toContentHeight(minHeight));
|
|
748
|
+
if (forcedHeight != null) contentHeight = Math.max(0, forcedHeight - padding[0] - padding[2] - border[0] - border[2]);
|
|
735
749
|
if (columnItems?.length) justifyColumn(ctx, columnItems, style?.justifyContent, contentHeight - (contentBottom - contentTop));
|
|
736
750
|
if (gridResult) alignGridContent(ctx, gridResult, style?.alignContent, contentHeight - (contentBottom - contentTop));
|
|
737
751
|
record.height = forcedHeight ?? contentHeight + padding[0] + padding[2] + border[0] + border[2];
|
|
738
|
-
|
|
752
|
+
// Summary's disclosure marker participates in its inline content.
|
|
753
|
+
const summaryMarker = box.node.tagName === "SUMMARY" && ["disclosure-open", "disclosure-closed"].includes(style?.listStyleType);
|
|
754
|
+
if (style?.display === "list-item" && !summaryMarker) placeListMarker(ctx, box, contentX, contentTop, firstFragment);
|
|
739
755
|
return { height: record.height, baseline };
|
|
740
756
|
}
|
|
741
757
|
|
|
@@ -783,6 +799,8 @@ function markerText(type, ordinal) {
|
|
|
783
799
|
case "disc": return "\u2022 ";
|
|
784
800
|
case "circle": return "\u25E6 ";
|
|
785
801
|
case "square": return "\u25AA ";
|
|
802
|
+
case "disclosure-closed": return "\u25B6 ";
|
|
803
|
+
case "disclosure-open": return "\u25BC ";
|
|
786
804
|
case "decimal": return `${ordinal}. `;
|
|
787
805
|
case "decimal-leading-zero": return `${String(ordinal).padStart(2, "0")}. `;
|
|
788
806
|
case "lower-alpha":
|
|
@@ -818,7 +836,97 @@ function roman(value) {
|
|
|
818
836
|
}
|
|
819
837
|
|
|
820
838
|
// ---------------------------------------------------------------------------
|
|
821
|
-
// Flex
|
|
839
|
+
// Flex layout (CSS Flexbox §9)
|
|
840
|
+
|
|
841
|
+
function layoutFlexColumn(ctx, box, x, width, y, minHeight, maxHeight) {
|
|
842
|
+
const style = box.style;
|
|
843
|
+
const gap = style?.rowGap ?? style?.gap ?? 0;
|
|
844
|
+
const definiteHeight = ctx.containing?.height;
|
|
845
|
+
// Most column containers have auto height. Without a main-axis constraint
|
|
846
|
+
// or definite basis there is no flexible free space to distribute, so a
|
|
847
|
+
// single layout pass suffices even for deeply nested application shells.
|
|
848
|
+
const unconstrained = definiteHeight == null && minHeight === 0 && maxHeight === Infinity
|
|
849
|
+
&& box.children.every(child => numberOrNull(resolveLength(child.style?.flexBasis, NaN)) == null);
|
|
850
|
+
if (unconstrained) {
|
|
851
|
+
let cursor = y;
|
|
852
|
+
let baseline = null;
|
|
853
|
+
const items = [];
|
|
854
|
+
for (const [index, child] of box.children.entries()) {
|
|
855
|
+
if (index) cursor += gap;
|
|
856
|
+
const horizontal = columnHorizontal(ctx, child, style, x, width);
|
|
857
|
+
const margin = child.style?.margin ?? ZERO_BOX;
|
|
858
|
+
items.push({boxStart:ctx.boxes.length,fragmentStart:ctx.fragments.length,
|
|
859
|
+
autoTop:margin[0] === "auto",autoBottom:margin[2] === "auto"});
|
|
860
|
+
const result = layoutBlock(ctx, child, horizontal.x, horizontal.width, cursor + numberOrZero(margin[0]));
|
|
861
|
+
cursor += numberOrZero(margin[0]) + result.height + numberOrZero(margin[2]);
|
|
862
|
+
baseline ??= result.baseline;
|
|
863
|
+
}
|
|
864
|
+
return {bottom:cursor,baseline,items};
|
|
865
|
+
}
|
|
866
|
+
const items = box.children.map(child => {
|
|
867
|
+
const childStyle = child.style;
|
|
868
|
+
const horizontal = columnHorizontal(ctx, child, style, x, width);
|
|
869
|
+
const margin = childStyle?.margin ?? ZERO_BOX;
|
|
870
|
+
const padding = paddingBox(childStyle, width);
|
|
871
|
+
const border = borderWidths(childStyle);
|
|
872
|
+
const extra = padding[0] + padding[2] + border[0] + border[2];
|
|
873
|
+
const contentBox = value => Math.max(0, childStyle?.boxSizing === "border-box" ? value - extra : value);
|
|
874
|
+
const resolve = value => numberOrNull(resolveLength(value, definiteHeight ?? NaN));
|
|
875
|
+
const measured = dryLayout(ctx, child, horizontal.width);
|
|
876
|
+
const content = Math.max(0, measured.height - extra);
|
|
877
|
+
const specified = resolve(childStyle?.height);
|
|
878
|
+
const basis = childStyle?.flexBasis ?? "auto";
|
|
879
|
+
const definiteBasis = resolve(basis);
|
|
880
|
+
const base = definiteBasis != null ? contentBox(definiteBasis)
|
|
881
|
+
: basis === "auto" && specified != null ? contentBox(specified) : content;
|
|
882
|
+
const minSize = resolve(childStyle?.minHeight);
|
|
883
|
+
// The automatic minimum is content-based except for scrollable items.
|
|
884
|
+
const scrollable = ["auto", "scroll", "hidden"].includes(childStyle?.overflowScrollY);
|
|
885
|
+
let min = minSize != null ? contentBox(minSize) : scrollable ? 0
|
|
886
|
+
: Math.min(content, specified == null ? Infinity : contentBox(specified));
|
|
887
|
+
const maxSize = resolve(childStyle?.maxHeight);
|
|
888
|
+
const max = maxSize == null ? Infinity : contentBox(maxSize);
|
|
889
|
+
min = Math.min(min, max);
|
|
890
|
+
return { box:child, horizontal, extra, base, min, max, naturalHeight:measured.height,
|
|
891
|
+
definite:definiteHeight != null || definiteBasis != null,
|
|
892
|
+
hypothetical:Math.min(max, Math.max(min, base)), grow:childStyle?.flexGrow ?? 0, shrink:childStyle?.flexShrink ?? 1,
|
|
893
|
+
// The shared main-axis solver names these left/right; columns use top/bottom.
|
|
894
|
+
marginLeft:numberOrZero(margin[0]), marginRight:numberOrZero(margin[2]),
|
|
895
|
+
autoTop:margin[0] === "auto", autoBottom:margin[2] === "auto" };
|
|
896
|
+
});
|
|
897
|
+
const gaps = gap * Math.max(0, items.length - 1);
|
|
898
|
+
const natural = gaps + items.reduce((sum, item) => sum + item.hypothetical + item.extra + item.marginLeft + item.marginRight, 0);
|
|
899
|
+
const available = Math.max(minHeight, Math.min(maxHeight, definiteHeight ?? natural));
|
|
900
|
+
resolveFlexibleLengths(items, available - gaps);
|
|
901
|
+
let cursor = y;
|
|
902
|
+
let baseline = null;
|
|
903
|
+
for (const [index, item] of items.entries()) {
|
|
904
|
+
if (index) cursor += gap;
|
|
905
|
+
item.boxStart = ctx.boxes.length;
|
|
906
|
+
item.fragmentStart = ctx.fragments.length;
|
|
907
|
+
const targetHeight = item.target + item.extra;
|
|
908
|
+
const forcedHeight = item.definite || Math.abs(targetHeight - item.naturalHeight) > 1e-9 ? targetHeight : null;
|
|
909
|
+
const result = layoutBlock(ctx, item.box, item.horizontal.x, item.horizontal.width,
|
|
910
|
+
cursor + item.marginLeft, false, forcedHeight);
|
|
911
|
+
cursor += item.marginLeft + result.height + item.marginRight;
|
|
912
|
+
baseline ??= result.baseline;
|
|
913
|
+
}
|
|
914
|
+
return { bottom:cursor, baseline, items };
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function columnHorizontal(ctx, child, style, x, width) {
|
|
918
|
+
const alignment = ["auto", "normal", undefined].includes(child.style?.alignSelf)
|
|
919
|
+
? style?.alignItems ?? "normal" : child.style.alignSelf;
|
|
920
|
+
const shrink = !["normal", "stretch"].includes(alignment);
|
|
921
|
+
const horizontal = resolveHorizontal(ctx, child, x, width, shrink);
|
|
922
|
+
const margin = child.style?.margin ?? ZERO_BOX;
|
|
923
|
+
if (shrink && margin[1] !== "auto" && margin[3] !== "auto") {
|
|
924
|
+
const free = width - horizontal.width - horizontal.marginLeft - horizontal.marginRight;
|
|
925
|
+
if (alignment === "center") horizontal.x += free / 2;
|
|
926
|
+
else if (alignment === "end") horizontal.x += free;
|
|
927
|
+
}
|
|
928
|
+
return horizontal;
|
|
929
|
+
}
|
|
822
930
|
|
|
823
931
|
function layoutFlexRow(ctx, box, x, width, y) {
|
|
824
932
|
const style = box.style;
|
|
@@ -912,7 +1020,7 @@ function flexItem(ctx, box, containerWidth) {
|
|
|
912
1020
|
// Sizes below are content-box sizes, as in Blink's FlexItem
|
|
913
1021
|
// (base_content_size, hypothetical_content_size); `extra` is padding+border.
|
|
914
1022
|
const style = box.style;
|
|
915
|
-
const padding = style
|
|
1023
|
+
const padding = paddingBox(style, containerWidth);
|
|
916
1024
|
const extra = padding[1] + padding[3] + borderWidths(style)[1] + borderWidths(style)[3];
|
|
917
1025
|
const contentBox = (value) => Math.max(0, style?.boxSizing === "border-box" ? value - extra : value);
|
|
918
1026
|
const margin = style?.margin ?? ZERO_BOX;
|
|
@@ -1124,7 +1232,7 @@ function tableStructure(node, flags) {
|
|
|
1124
1232
|
target.push({
|
|
1125
1233
|
node: child,
|
|
1126
1234
|
style: child.style ?? null,
|
|
1127
|
-
cells: child.
|
|
1235
|
+
cells: [...boxChildren(child)].filter((cell) => cell.type === "table-cell").map((cell) => tableCell(cell, rowFlags)),
|
|
1128
1236
|
});
|
|
1129
1237
|
} else if (child.type === "table-cell") {
|
|
1130
1238
|
if (!anonymous) target.push(anonymous = { node: null, style: null, cells: [] });
|
|
@@ -1137,16 +1245,16 @@ function tableStructure(node, flags) {
|
|
|
1137
1245
|
addRows(body, loose, flags);
|
|
1138
1246
|
loose = [];
|
|
1139
1247
|
};
|
|
1140
|
-
for (const child of node
|
|
1248
|
+
for (const child of boxChildren(node)) {
|
|
1141
1249
|
if (child.type === "table-row" || child.type === "table-cell") {
|
|
1142
1250
|
loose.push(child);
|
|
1143
1251
|
continue;
|
|
1144
1252
|
}
|
|
1145
1253
|
flushLoose();
|
|
1146
1254
|
if (child.type === "table-caption") captions.push(buildBox(child, inheritedFlags(child, flags), true));
|
|
1147
|
-
else if (child.type === "table-header-group") addRows(header, child
|
|
1148
|
-
else if (child.type === "table-footer-group") addRows(footer, child
|
|
1149
|
-
else if (child.type === "table-row-group") addRows(body, child
|
|
1255
|
+
else if (child.type === "table-header-group") addRows(header, boxChildren(child), inheritedFlags(child, flags));
|
|
1256
|
+
else if (child.type === "table-footer-group") addRows(footer, boxChildren(child), inheritedFlags(child, flags));
|
|
1257
|
+
else if (child.type === "table-row-group") addRows(body, boxChildren(child), inheritedFlags(child, flags));
|
|
1150
1258
|
}
|
|
1151
1259
|
flushLoose();
|
|
1152
1260
|
return { rows: [...header, ...body, ...footer], captions, grid: null };
|
|
@@ -1338,15 +1446,44 @@ function gridHorizontal(ctx, containerStyle, item, areaX, areaWidth) {
|
|
|
1338
1446
|
return placed;
|
|
1339
1447
|
}
|
|
1340
1448
|
|
|
1341
|
-
/** Expands track lists and places items (§7.2, §8.5
|
|
1449
|
+
/** Expands track lists and places items (§7.2, §8.5). */
|
|
1342
1450
|
function gridStructure(ctx, box, availableWidth, columnGap, definite) {
|
|
1343
1451
|
const style = box.style;
|
|
1344
1452
|
const explicitColumns = expandTrackList(style?.gridTemplateColumns, definite ? availableWidth : null, columnGap);
|
|
1345
1453
|
const explicitRows = expandTrackList(style?.gridTemplateRows, null, style?.rowGap ?? 0);
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1454
|
+
const areas = style?.gridTemplateAreas;
|
|
1455
|
+
const explicitColumnCount = Math.max(explicitColumns.tracks.length, areas?.columns ?? 0);
|
|
1456
|
+
const explicitRowCount = Math.max(explicitRows.tracks.length, areas?.rows ?? 0);
|
|
1457
|
+
const items = box.children.map((child) => ({ box: child,
|
|
1458
|
+
...gridLines(child.style, explicitColumnCount, "Column", areas),
|
|
1459
|
+
...gridLines(child.style, explicitRowCount, "Row", areas) }));
|
|
1460
|
+
// The column-flow algorithm is the row-flow algorithm with its axes exchanged.
|
|
1461
|
+
const columnFlow = style?.gridAutoFlow?.column === true;
|
|
1462
|
+
if (columnFlow) for (const item of items) transposeGridItem(item);
|
|
1463
|
+
let { columnCount, rowCount } = placeGridItems(items,
|
|
1464
|
+
columnFlow ? explicitRowCount : explicitColumnCount,
|
|
1465
|
+
columnFlow ? explicitColumnCount : explicitRowCount, style?.gridAutoFlow?.dense === true);
|
|
1466
|
+
if (columnFlow) {
|
|
1467
|
+
for (const item of items) transposeGridItem(item);
|
|
1468
|
+
[columnCount, rowCount] = [rowCount, columnCount];
|
|
1469
|
+
}
|
|
1470
|
+
const columnTracks = Array.from({ length: columnCount }, (_, index) =>
|
|
1471
|
+
explicitColumns.tracks[index] ?? cycle(style?.gridAutoColumns, index - explicitColumns.tracks.length));
|
|
1472
|
+
const rowTracks = Array.from({ length: rowCount }, (_, index) =>
|
|
1473
|
+
explicitRows.tracks[index] ?? cycle(style?.gridAutoRows, index - explicitRows.tracks.length));
|
|
1474
|
+
// auto-fit: repeated tracks nobody occupies collapse (§7.2.3.2).
|
|
1475
|
+
const columnCollapsed = columnTracks.map((_, index) => explicitColumns.autoFit.has(index)
|
|
1476
|
+
&& !items.some((item) => index >= item.column && index < item.column + item.columnSpan));
|
|
1477
|
+
return { items, columnTracks, rowTracks, columnCollapsed };
|
|
1478
|
+
}
|
|
1348
1479
|
|
|
1349
|
-
|
|
1480
|
+
function transposeGridItem(item) {
|
|
1481
|
+
[item.row, item.column] = [item.column, item.row];
|
|
1482
|
+
[item.rowSpan, item.columnSpan] = [item.columnSpan, item.rowSpan];
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
function placeGridItems(items, explicitColumnCount, explicitRowCount, dense) {
|
|
1486
|
+
let columnCount = Math.max(1, explicitColumnCount);
|
|
1350
1487
|
for (const item of items) {
|
|
1351
1488
|
if (item.column != null) columnCount = Math.max(columnCount, item.column + item.columnSpan);
|
|
1352
1489
|
else columnCount = Math.max(columnCount, item.columnSpan);
|
|
@@ -1376,7 +1513,6 @@ function gridStructure(ctx, box, availableWidth, columnGap, definite) {
|
|
|
1376
1513
|
occupy(item);
|
|
1377
1514
|
}
|
|
1378
1515
|
// 3. Auto-placed items walk a cursor forward (sparse) or restart (dense).
|
|
1379
|
-
const dense = style?.gridAutoFlow?.dense === true;
|
|
1380
1516
|
let cursorRow = 0;
|
|
1381
1517
|
let cursorColumn = 0;
|
|
1382
1518
|
for (const item of items) {
|
|
@@ -1406,15 +1542,8 @@ function gridStructure(ctx, box, availableWidth, columnGap, definite) {
|
|
|
1406
1542
|
occupy(item);
|
|
1407
1543
|
}
|
|
1408
1544
|
|
|
1409
|
-
const rowCount = Math.max(
|
|
1410
|
-
|
|
1411
|
-
explicitColumns.tracks[index] ?? cycle(style?.gridAutoColumns, index - explicitColumns.tracks.length));
|
|
1412
|
-
const rowTracks = Array.from({ length: rowCount }, (_, index) =>
|
|
1413
|
-
explicitRows.tracks[index] ?? cycle(style?.gridAutoRows, index - explicitRows.tracks.length));
|
|
1414
|
-
// auto-fit: repeated tracks nobody occupies collapse (§7.2.3.2).
|
|
1415
|
-
const columnCollapsed = columnTracks.map((_, index) => explicitColumns.autoFit.has(index)
|
|
1416
|
-
&& !items.some((item) => index >= item.column && index < item.column + item.columnSpan));
|
|
1417
|
-
return { items, columnTracks, rowTracks, columnCollapsed };
|
|
1545
|
+
const rowCount = Math.max(explicitRowCount, ...items.map((item) => item.row + item.rowSpan), 0);
|
|
1546
|
+
return { columnCount, rowCount };
|
|
1418
1547
|
}
|
|
1419
1548
|
|
|
1420
1549
|
function cycle(tracks, index) {
|
|
@@ -1422,13 +1551,22 @@ function cycle(tracks, index) {
|
|
|
1422
1551
|
return list[((index % list.length) + list.length) % list.length];
|
|
1423
1552
|
}
|
|
1424
1553
|
|
|
1425
|
-
function gridLines(style, explicitCount, axis) {
|
|
1554
|
+
function gridLines(style, explicitCount, axis, templateAreas) {
|
|
1426
1555
|
const start = style?.[`grid${axis}Start`] ?? "auto";
|
|
1427
1556
|
const end = style?.[`grid${axis}End`] ?? "auto";
|
|
1428
1557
|
// Line numbers are 1-based; negatives count back from the explicit end line.
|
|
1429
1558
|
const index = (line) => line > 0 ? line - 1 : Math.max(0, explicitCount + 1 + line);
|
|
1430
|
-
|
|
1431
|
-
|
|
1559
|
+
const areaLine = (name, end) => {
|
|
1560
|
+
if (!name || !templateAreas) return null;
|
|
1561
|
+
const key = axis.toLowerCase();
|
|
1562
|
+
const direct = templateAreas.areas[name];
|
|
1563
|
+
if (direct) return direct[end ? `${key}End` : key];
|
|
1564
|
+
const suffix = /^(.*)-(start|end)$/.exec(name);
|
|
1565
|
+
const area = suffix && templateAreas.areas[suffix[1]];
|
|
1566
|
+
return area ? area[suffix[2] === "end" ? `${key}End` : key] : null;
|
|
1567
|
+
};
|
|
1568
|
+
let from = start.line != null ? index(start.line) : areaLine(start.name, false);
|
|
1569
|
+
let to = end.line != null ? index(end.line) : areaLine(end.name, true);
|
|
1432
1570
|
let span = 1;
|
|
1433
1571
|
if (from != null && to != null) {
|
|
1434
1572
|
if (to < from) [from, to] = [to, from];
|
|
@@ -1445,10 +1583,6 @@ function gridLines(style, explicitCount, axis) {
|
|
|
1445
1583
|
return axis === "Column" ? { column: from, columnSpan: span } : { row: from, rowSpan: span };
|
|
1446
1584
|
}
|
|
1447
1585
|
|
|
1448
|
-
function rowLines(style, explicitCount) {
|
|
1449
|
-
return gridLines(style, explicitCount, "Row");
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
1586
|
/** Expands repeat() and resolves auto-fill/auto-fit repetitions (§7.2.3.2). */
|
|
1453
1587
|
function expandTrackList(list, available, gap) {
|
|
1454
1588
|
const tracks = [];
|
|
@@ -1503,7 +1637,7 @@ function minimumContribution(box, minContent, available) {
|
|
|
1503
1637
|
const margins = numberOrZero(margin[1]) + numberOrZero(margin[3]);
|
|
1504
1638
|
const specified = numberOrNull(resolveLength(style?.minWidth, available));
|
|
1505
1639
|
if (specified != null) {
|
|
1506
|
-
const padding = style
|
|
1640
|
+
const padding = paddingBox(style, available);
|
|
1507
1641
|
const extra = padding[1] + padding[3] + borderWidths(style)[1] + borderWidths(style)[3];
|
|
1508
1642
|
return (style.boxSizing === "border-box" ? Math.max(specified, extra) : specified + extra) + margins;
|
|
1509
1643
|
}
|
|
@@ -1693,7 +1827,7 @@ function dryLayout(ctx, box, width) {
|
|
|
1693
1827
|
/** Resolves the used border-box width and horizontal margins of a block-level box. */
|
|
1694
1828
|
function resolveHorizontal(ctx, box, containingX, containingWidth, shrink, percentageBasis = containingWidth) {
|
|
1695
1829
|
const style = box.style;
|
|
1696
|
-
const padding = style
|
|
1830
|
+
const padding = paddingBox(style, percentageBasis);
|
|
1697
1831
|
const extra = padding[1] + padding[3] + borderWidths(style)[1] + borderWidths(style)[3];
|
|
1698
1832
|
const borderBox = (value) => style?.boxSizing === "border-box" ? value : value + extra;
|
|
1699
1833
|
const margin = style?.margin ?? ZERO_BOX;
|
|
@@ -1729,7 +1863,7 @@ function maxContentWidth(ctx, box) {
|
|
|
1729
1863
|
const cached = ctx.maxContent.get(cacheKey);
|
|
1730
1864
|
if (cached != null) return cached;
|
|
1731
1865
|
const style = box.style;
|
|
1732
|
-
const padding = style
|
|
1866
|
+
const padding = paddingBox(style);
|
|
1733
1867
|
const extra = padding[1] + padding[3] + borderWidths(style)[1] + borderWidths(style)[3];
|
|
1734
1868
|
let width;
|
|
1735
1869
|
const specified = numberOrNull(style?.width);
|
|
@@ -1772,7 +1906,7 @@ function minContentWidth(ctx, box) {
|
|
|
1772
1906
|
const cached = ctx.minContent.get(cacheKey);
|
|
1773
1907
|
if (cached != null) return cached;
|
|
1774
1908
|
const style = box.style;
|
|
1775
|
-
const padding = style
|
|
1909
|
+
const padding = paddingBox(style);
|
|
1776
1910
|
const extra = padding[1] + padding[3] + borderWidths(style)[1] + borderWidths(style)[3];
|
|
1777
1911
|
const specified = numberOrNull(style?.width);
|
|
1778
1912
|
const outer = (child) => {
|
|
@@ -1821,6 +1955,15 @@ function collapsesThroughTop(box) {
|
|
|
1821
1955
|
&& inFlowChildren(box)[0]?.kind === "block";
|
|
1822
1956
|
}
|
|
1823
1957
|
|
|
1958
|
+
function selfCollapsing(box) {
|
|
1959
|
+
return !box.bfc && box.kind === "block" && !box.replaced
|
|
1960
|
+
&& borderWidths(box.style).every(value => value === 0)
|
|
1961
|
+
&& (box.style?.padding ?? ZERO_BOX).every(value => value === 0)
|
|
1962
|
+
&& (box.style?.height === "auto" || box.style?.height === 0)
|
|
1963
|
+
&& (numberOrNull(box.style?.minHeight) ?? 0) <= 0
|
|
1964
|
+
&& inFlowChildren(box).every(selfCollapsing);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1824
1967
|
function collapsesThroughBottom(box) {
|
|
1825
1968
|
return !box.bfc && box.kind === "block" && !box.replaced
|
|
1826
1969
|
&& borderWidths(box.style)[2] === 0 && (box.style?.padding ?? ZERO_BOX)[2] === 0
|
|
@@ -1874,6 +2017,14 @@ function layoutInline(ctx, ifc, x, width, y) {
|
|
|
1874
2017
|
// Percentage sizes of inline images.
|
|
1875
2018
|
atomFor.containing = ctx.containing;
|
|
1876
2019
|
const lines = breakLines(ctx, paragraph, width, atomFor);
|
|
2020
|
+
// Legacy line clamping applies to a vertical -webkit-box, not ordinary blocks.
|
|
2021
|
+
const clamp = ifc.style?.webkitBoxOrient === "vertical"
|
|
2022
|
+
&& ["-webkit-box", "-webkit-inline-box"].includes(ifc.style?.display)
|
|
2023
|
+
? ifc.style.webkitLineClamp : null;
|
|
2024
|
+
if (clamp && lines.length > clamp) {
|
|
2025
|
+
lines.length = clamp;
|
|
2026
|
+
lines[clamp - 1] = { ...lines[clamp - 1], clamped: true };
|
|
2027
|
+
}
|
|
1877
2028
|
let cursor = y;
|
|
1878
2029
|
let baseline = null;
|
|
1879
2030
|
for (const line of lines) {
|
|
@@ -2048,7 +2199,7 @@ function mayBreakWithinWords(paragraph, piece) {
|
|
|
2048
2199
|
* room for "…". Mutates `segments`; returns the ellipsis to append, or null.
|
|
2049
2200
|
*/
|
|
2050
2201
|
function truncateForEllipsis(ctx, style, paragraph, segments, line, width) {
|
|
2051
|
-
if (style?.textOverflow !== "ellipsis" || style.overflowX !== "clip" || line.width <= width + 0.5) return null;
|
|
2202
|
+
if (!line.clamped && (style?.textOverflow !== "ellipsis" || style.overflowX !== "clip" || line.width <= width + 0.5)) return null;
|
|
2052
2203
|
const lastText = [...segments].reverse().find((segment) => segment.run.kind === "text");
|
|
2053
2204
|
if (!lastText) return null;
|
|
2054
2205
|
const text = "\u2026";
|
|
@@ -2088,7 +2239,7 @@ function truncateForEllipsis(ctx, style, paragraph, segments, line, width) {
|
|
|
2088
2239
|
function inlineImageMargins(node) {
|
|
2089
2240
|
const margin = node.style?.margin ?? ZERO_BOX;
|
|
2090
2241
|
const border = borderWidths(node.style);
|
|
2091
|
-
const padding = node.style
|
|
2242
|
+
const padding = paddingBox(node.style);
|
|
2092
2243
|
return {
|
|
2093
2244
|
left: numberOrZero(margin[3]) + border[3] + padding[3],
|
|
2094
2245
|
right: numberOrZero(margin[1]) + border[1] + padding[1],
|
|
@@ -2099,7 +2250,7 @@ function inlineImageMargins(node) {
|
|
|
2099
2250
|
function inlineImageVerticalInsets(node) {
|
|
2100
2251
|
const margin = node.style?.margin ?? ZERO_BOX;
|
|
2101
2252
|
const border = borderWidths(node.style);
|
|
2102
|
-
const padding = node.style
|
|
2253
|
+
const padding = paddingBox(node.style);
|
|
2103
2254
|
return {
|
|
2104
2255
|
top: numberOrZero(margin[0]) + border[0] + padding[0],
|
|
2105
2256
|
bottom: numberOrZero(margin[2]) + border[2] + padding[2],
|
|
@@ -2128,7 +2279,7 @@ function inlineImageTop(style, height, metrics) {
|
|
|
2128
2279
|
|
|
2129
2280
|
function inlineInsets(style) {
|
|
2130
2281
|
const margin = style?.margin ?? ZERO_BOX;
|
|
2131
|
-
const padding = style
|
|
2282
|
+
const padding = paddingBox(style);
|
|
2132
2283
|
const border = borderWidths(style);
|
|
2133
2284
|
return {
|
|
2134
2285
|
left: numberOrZero(margin[3]) + border[3] + padding[3],
|
|
@@ -2320,7 +2471,7 @@ function placeLine(ctx, ifc, paragraph, line, x, width, y, atomFor) {
|
|
|
2320
2471
|
// Its background and borders, around the content box.
|
|
2321
2472
|
if (isDecorated(run.node.style)) {
|
|
2322
2473
|
const border = borderWidths(run.node.style);
|
|
2323
|
-
const padding = run.node.style.
|
|
2474
|
+
const padding = paddingBox(run.node.style, ctx.containing?.width);
|
|
2324
2475
|
ctx.boxes.push({
|
|
2325
2476
|
nodeId: run.nodeId,
|
|
2326
2477
|
x: imageX - padding[3] - border[3],
|
|
@@ -2396,7 +2547,7 @@ function placeLine(ctx, ifc, paragraph, line, x, width, y, atomFor) {
|
|
|
2396
2547
|
for (const [node, edge] of edges) {
|
|
2397
2548
|
const nodeStyle = paragraph.inlineState.get(node)?.style;
|
|
2398
2549
|
if (!isDecorated(nodeStyle) || edge.first == null) continue;
|
|
2399
|
-
const padding = nodeStyle.
|
|
2550
|
+
const padding = paddingBox(nodeStyle, ctx.containing?.width);
|
|
2400
2551
|
const border = borderWidths(nodeStyle);
|
|
2401
2552
|
const metrics = textMetrics(ctx, nodeStyle);
|
|
2402
2553
|
const left = edge.left ?? edge.first;
|
|
@@ -2506,7 +2657,7 @@ function cachedMetrics(fontMetrics) {
|
|
|
2506
2657
|
const byStyle = new WeakMap();
|
|
2507
2658
|
const metricsFor = (style) => {
|
|
2508
2659
|
const fontSize = style?.fontSize ?? 16;
|
|
2509
|
-
const key = `${fontSize}/${style?.fontWeight ?? 400}/${style?.fontFamily?.join(",")}`;
|
|
2660
|
+
const key = `${fontSize}/${style?.fontWeight ?? 400}/${style?.fontStyle ?? "normal"}/${style?.fontStretch ?? 100}/${style?.fontFamily?.join(",")}`;
|
|
2510
2661
|
let metrics = cache.get(key);
|
|
2511
2662
|
if (!metrics) {
|
|
2512
2663
|
metrics = typeof fontMetrics === "function" ? fontMetrics(style ?? null) : null;
|
|
@@ -2531,12 +2682,12 @@ function cachedMetrics(fontMetrics) {
|
|
|
2531
2682
|
* whatever box it gets), so each dimension falls back to 300x150 alone.
|
|
2532
2683
|
*/
|
|
2533
2684
|
function frameDimensions(node, referenceWidth, referenceHeight) {
|
|
2534
|
-
const resolve = (cssValue,
|
|
2535
|
-
const value = cssValue !== "auto" && cssValue != null ? resolveLength(cssValue, reference ?? NaN) :
|
|
2685
|
+
const resolve = (cssValue, fallback, reference) => {
|
|
2686
|
+
const value = cssValue !== "auto" && cssValue != null ? resolveLength(cssValue, reference ?? NaN) : null;
|
|
2536
2687
|
return Number.isFinite(value) ? value : fallback;
|
|
2537
2688
|
};
|
|
2538
|
-
let width = resolve(node.style?.width,
|
|
2539
|
-
const height = resolve(node.style?.height,
|
|
2689
|
+
let width = resolve(node.style?.width, 300, referenceWidth);
|
|
2690
|
+
const height = resolve(node.style?.height, 150, referenceHeight);
|
|
2540
2691
|
const maximum = node.style?.maxWidth !== "none" && node.style?.maxWidth != null ? resolveLength(node.style.maxWidth, referenceWidth) : NaN;
|
|
2541
2692
|
if (Number.isFinite(maximum) && width > maximum) width = maximum;
|
|
2542
2693
|
return { width: Math.max(0, width), height: Math.max(0, height) };
|
|
@@ -2568,25 +2719,29 @@ function svgDimensions(node, referenceWidth, referenceHeight) {
|
|
|
2568
2719
|
function imageDimensions(node, referenceWidth, referenceHeight = null) {
|
|
2569
2720
|
if (node.tagName === "IFRAME") return frameDimensions(node, referenceWidth, referenceHeight);
|
|
2570
2721
|
if (node.tagName === "SVG") return svgDimensions(node, referenceWidth, referenceHeight);
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
const
|
|
2722
|
+
// A canvas's attributes are its intrinsic bitmap dimensions. Its cached
|
|
2723
|
+
// paint snapshot can still describe the previous bitmap during layout.
|
|
2724
|
+
const canvas = node.tagName === "CANVAS";
|
|
2725
|
+
const intrinsicWidth = (canvas ? node.widthAttribute : node.resource?.width) ?? node.widthAttribute ?? 300;
|
|
2726
|
+
const intrinsicHeight = (canvas ? node.heightAttribute : node.resource?.height) ?? node.heightAttribute ?? 150;
|
|
2727
|
+
const emptyCanvas = node.tagName === "CANVAS" && !(intrinsicWidth > 0 && intrinsicHeight > 0);
|
|
2728
|
+
const ratio = emptyCanvas ? null : intrinsicWidth / intrinsicHeight;
|
|
2574
2729
|
const cssWidth = node.style?.width;
|
|
2575
2730
|
const cssHeight = node.style?.height;
|
|
2576
2731
|
let width = cssWidth !== "auto" && cssWidth != null
|
|
2577
2732
|
? resolveLength(cssWidth, referenceWidth)
|
|
2578
|
-
:
|
|
2733
|
+
: null;
|
|
2579
2734
|
let height = cssHeight !== "auto" && cssHeight != null
|
|
2580
2735
|
? resolveLength(cssHeight, referenceHeight ?? NaN)
|
|
2581
|
-
:
|
|
2736
|
+
: null;
|
|
2582
2737
|
if (!Number.isFinite(width)) width = null;
|
|
2583
2738
|
if (!Number.isFinite(height)) height = null;
|
|
2584
2739
|
if (width == null && height == null) [width, height] = [intrinsicWidth, intrinsicHeight];
|
|
2585
|
-
else if (width == null) width = height * ratio;
|
|
2586
|
-
else if (height == null) height = width / ratio;
|
|
2740
|
+
else if (width == null) width = ratio == null ? intrinsicWidth : height * ratio;
|
|
2741
|
+
else if (height == null) height = ratio == null ? intrinsicHeight : width / ratio;
|
|
2587
2742
|
if (node.style?.maxWidth !== "none" && node.style?.maxWidth != null) {
|
|
2588
2743
|
const maximum = resolveLength(node.style.maxWidth, referenceWidth);
|
|
2589
|
-
if (Number.isFinite(maximum) && width > maximum) [width, height] = [maximum, maximum / ratio];
|
|
2744
|
+
if (Number.isFinite(maximum) && width > maximum) [width, height] = [maximum, ratio == null ? height : maximum / ratio];
|
|
2590
2745
|
}
|
|
2591
2746
|
if (node.tagName === "VIDEO") {
|
|
2592
2747
|
if (Number.isFinite(referenceWidth) && width > referenceWidth) [width, height] = [referenceWidth, referenceWidth / ratio];
|
|
@@ -2595,7 +2750,8 @@ function imageDimensions(node, referenceWidth, referenceHeight = null) {
|
|
|
2595
2750
|
if (Number.isFinite(minHeight) && height < minHeight) height = minHeight;
|
|
2596
2751
|
if (Number.isFinite(maxHeight) && height > maxHeight) [width, height] = [maxHeight * ratio, maxHeight];
|
|
2597
2752
|
}
|
|
2598
|
-
|
|
2753
|
+
const minimum = node.tagName === "CANVAS" ? 0 : 1;
|
|
2754
|
+
return { width: Math.max(minimum, width), height: Math.max(minimum, height) };
|
|
2599
2755
|
}
|
|
2600
2756
|
|
|
2601
2757
|
function positiveInteger(value, fallback) {
|
|
@@ -2622,8 +2778,10 @@ function resolveLength(value, reference) {
|
|
|
2622
2778
|
if (!value || typeof value !== "object") return value;
|
|
2623
2779
|
if (value.unit === "%") return reference * value.value / 100;
|
|
2624
2780
|
if (value.unit === "math" && value.kind === "calc") return value.px + reference * value.percent / 100;
|
|
2781
|
+
if (value.unit === "math" && value.kind === "scale") return value.factor * resolveLength(value.value, reference);
|
|
2625
2782
|
if (value.unit === "math") {
|
|
2626
2783
|
const values = value.values.map((part) => resolveLength(part, reference));
|
|
2784
|
+
if (value.kind === "sum") return values.reduce((sum, part) => sum + part, 0);
|
|
2627
2785
|
if (value.kind === "min") return Math.min(...values);
|
|
2628
2786
|
if (value.kind === "max") return Math.max(...values);
|
|
2629
2787
|
return Math.max(values[0], Math.min(values[1], values[2]));
|