@react-x11/components 0.14.1 → 0.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/code-editor/node.d.ts +13 -0
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +23 -5
- package/dist/code-editor/node.js.map +1 -1
- package/dist/html/css/cascade.d.ts +171 -26
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +616 -216
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +25 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +307 -19
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/content.d.ts +11 -6
- package/dist/html/css/content.d.ts.map +1 -1
- package/dist/html/css/content.js +171 -12
- package/dist/html/css/content.js.map +1 -1
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +62 -9
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/shapes.d.ts +18 -0
- package/dist/html/css/shapes.d.ts.map +1 -1
- package/dist/html/css/shapes.js +21 -1
- package/dist/html/css/shapes.js.map +1 -1
- package/dist/html/css/style.d.ts +42 -5
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +74 -12
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +15 -0
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +5 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +18 -9
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +7 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +29 -9
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +141 -52
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +16 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +144 -23
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +11 -9
- package/dist/html/layout/css-grid.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +1631 -221
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +15 -3
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +23 -8
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/table.d.ts +27 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +886 -324
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +35 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +118 -26
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +558 -178
- package/dist/html/paint.js.map +1 -1
- package/dist/html/resources.js +14 -1
- package/dist/html/resources.js.map +1 -1
- package/dist/html/svg.d.ts +23 -7
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +264 -59
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.d.ts.map +1 -1
- package/dist/html/widgets.js +57 -4
- package/dist/html/widgets.js.map +1 -1
- package/package.json +3 -3
- package/src/code-editor/node.ts +24 -5
- package/src/html/css/cascade.ts +697 -221
- package/src/html/css/color.ts +345 -18
- package/src/html/css/content.ts +173 -15
- package/src/html/css/parse.ts +60 -8
- package/src/html/css/shapes.ts +35 -1
- package/src/html/css/style.ts +122 -17
- package/src/html/css/ua.ts +15 -0
- package/src/html/css/values.ts +23 -9
- package/src/html/index.ts +7 -0
- package/src/html/layout/block.ts +149 -58
- package/src/html/layout/boxes.ts +171 -27
- package/src/html/layout/css-grid.ts +12 -9
- package/src/html/layout/flex.ts +1843 -212
- package/src/html/layout/inline.ts +27 -7
- package/src/html/layout/table.ts +961 -332
- package/src/html/node.ts +151 -27
- package/src/html/paint.ts +692 -234
- package/src/html/resources.ts +13 -1
- package/src/html/svg.ts +299 -56
- package/src/html/widgets.ts +61 -4
package/dist/html/paint.js
CHANGED
|
@@ -29,7 +29,7 @@ import { contained, placedMatrix } from './layout/block.js';
|
|
|
29
29
|
import { BOX_RAISES, LINE_BOX_RAISES, PADDED_FACES, Box, CLAMPED, CLIPPED_CELLS, COLLAPSED_CELLS, COLUMN_LINES, COLUMN_PIECES, COLUMN_ROWS, columned, FADED_BLOCKS, PAINT_ORDER, INLINE_OFFSETS, MOVED_OFF_LINES, SHADOWED_TEXT, SHIFTED_LINES, TEXT_RAISES, TEXT_SHIFTS, } from './layout/boxes.js';
|
|
30
30
|
import { CLIPPED_TEXT, clipBoxOf, depthOf, inklessLayout, layoutOffsetOf, layoutOffsets, } from './layout/inline.js';
|
|
31
31
|
import { tableGrid } from './layout/grid.js';
|
|
32
|
-
import { SvgDrawing, inlineDrawing } from './svg.js';
|
|
32
|
+
import { SvgDrawing, concreteSize, inlineDrawing } from './svg.js';
|
|
33
33
|
/**
|
|
34
34
|
* The bounds of everything a box and its descendants draw, in document
|
|
35
35
|
* coordinates, into `boundsX`…, with the text `position: relative` moved off
|
|
@@ -696,7 +696,8 @@ function originBox(frame, options, style) {
|
|
|
696
696
|
* it falls nearest, as browsers snap a box — boxes that meet share the
|
|
697
697
|
* column their edge is in, and a rule 1.33px wide is one pixel, not two —
|
|
698
698
|
* with the border box's corners less the widths between (5.3), where
|
|
699
|
-
* `rounded` asks for them
|
|
699
|
+
* `rounded` asks for them — cut to what the paint reaches (`clampArea`).
|
|
700
|
+
* Null where it has no area there.
|
|
700
701
|
*/
|
|
701
702
|
function clipArea(box, options, style, rounded) {
|
|
702
703
|
const left = box.x + options.originX;
|
|
@@ -716,7 +717,7 @@ function clipArea(box, options, style, rounded) {
|
|
|
716
717
|
if (corners && (t || r || b || l)) {
|
|
717
718
|
corners = spreadCorners(corners, box.width, frameHeight(box), -t, -r, -b, -l);
|
|
718
719
|
}
|
|
719
|
-
return
|
|
720
|
+
return clampArea(options, x, y, w, h, corners);
|
|
720
721
|
}
|
|
721
722
|
/** Where a box's background and border go: its border box, which for a
|
|
722
723
|
* table leaves out the captions around it (CSS 2.1 17.4). */
|
|
@@ -1364,10 +1365,9 @@ const ROW_REACH = 2048;
|
|
|
1364
1365
|
*/
|
|
1365
1366
|
function frameImages(ctx, frame, options) {
|
|
1366
1367
|
return (layer) => {
|
|
1367
|
-
const
|
|
1368
|
-
const area = painted && clampRect(options, painted.x, painted.y, painted.w, painted.h);
|
|
1368
|
+
const area = clipArea(frame, options, layer, true);
|
|
1369
1369
|
if (area) {
|
|
1370
|
-
paintBackgroundImage(ctx, layer, area, originBox(frame, options, layer), options,
|
|
1370
|
+
paintBackgroundImage(ctx, layer, area, originBox(frame, options, layer), options, area.corners);
|
|
1371
1371
|
}
|
|
1372
1372
|
};
|
|
1373
1373
|
}
|
|
@@ -1377,6 +1377,8 @@ function shadowReach(blur) {
|
|
|
1377
1377
|
return blur > 0 ? Math.ceil(blur * 1.5) : 0;
|
|
1378
1378
|
}
|
|
1379
1379
|
const SQUARE = { x: [0, 0, 0, 0], y: [0, 0, 0, 0] };
|
|
1380
|
+
/** A rectangle with no area, where a ring has no inside. */
|
|
1381
|
+
const NOWHERE = { x: 0, y: 0, width: 0, height: 0 };
|
|
1380
1382
|
/** Corners shrunk by `by` — an inner shadow's spread — or grown where it
|
|
1381
1383
|
* is negative; a square corner stays square. */
|
|
1382
1384
|
function grownCorners(c, by) {
|
|
@@ -1444,28 +1446,28 @@ function paintShadows(ctx, box, options, inset) {
|
|
|
1444
1446
|
if (!clampRect(options, shape.x - reach, shape.y - reach, shape.width + 2 * reach, shape.height + 2 * reach)) {
|
|
1445
1447
|
continue;
|
|
1446
1448
|
}
|
|
1447
|
-
|
|
1449
|
+
const around = spreadCorners(corners, rect.width, rect.height, s.spread, s.spread, s.spread, s.spread) ?? SQUARE;
|
|
1450
|
+
// Cut, as a background is (`clampArea`), to what the paint reaches
|
|
1448
1451
|
// and as far again as the blur does, so the cut edges cast nothing
|
|
1449
|
-
// on it
|
|
1450
|
-
//
|
|
1451
|
-
|
|
1452
|
-
|
|
1452
|
+
// on it, and the box with it, which a ring is the band outside and
|
|
1453
|
+
// the shadow is clipped out of. A shadow down a box thousands of
|
|
1454
|
+
// pixels tall went to the server whole, and its outline, sent in
|
|
1455
|
+
// 16.16 fixed point, threw.
|
|
1456
|
+
const [cut, own] = clampPair(options, reach + CLAMP_PAD, { rect: shape, corners: around }, { rect, corners });
|
|
1453
1457
|
if (!cut)
|
|
1454
1458
|
continue;
|
|
1455
|
-
|
|
1456
|
-
const own = clampAround(options, rect, within) ?? rect;
|
|
1457
|
-
const around = spreadCorners(corners, rect.width, rect.height, s.spread, s.spread, s.spread, s.spread) ?? SQUARE;
|
|
1459
|
+
const { x, y, width, height } = cut.rect;
|
|
1458
1460
|
const color = inkColor(s.color, style.color);
|
|
1459
1461
|
if (!(s.blur > 0) && !s.x && !s.y && s.spread >= 0 && !covered) {
|
|
1460
1462
|
// a ring about the box: the band between it and the spread
|
|
1461
1463
|
ctx.fillStyle = color;
|
|
1462
|
-
fillRing(ctx,
|
|
1464
|
+
fillRing(ctx, cut.rect, cut.corners, own ? own.rect : NOWHERE, own ? own.corners : SQUARE);
|
|
1463
1465
|
continue;
|
|
1464
1466
|
}
|
|
1465
1467
|
if (!(s.blur > 0) && covered) {
|
|
1466
1468
|
// a hard shadow, under a box that hides what falls under it
|
|
1467
1469
|
fillShadow(ctx, s, color, (dx) => {
|
|
1468
|
-
roundedRect(ctx,
|
|
1470
|
+
roundedRect(ctx, x - dx, y, width, height, cut.corners);
|
|
1469
1471
|
}, 0);
|
|
1470
1472
|
continue;
|
|
1471
1473
|
}
|
|
@@ -1474,13 +1476,16 @@ function paintShadows(ctx, box, options, inset) {
|
|
|
1474
1476
|
if (clipped) {
|
|
1475
1477
|
ctx.save();
|
|
1476
1478
|
ctx.beginPath();
|
|
1477
|
-
ctx.rect(
|
|
1478
|
-
|
|
1479
|
+
ctx.rect(x - reach - 1, y - reach - 1, width + 2 * reach + 2, height + 2 * reach + 2);
|
|
1480
|
+
if (own) {
|
|
1481
|
+
const { rect: r } = own;
|
|
1482
|
+
roundedRect(ctx, r.x, r.y, r.width, r.height, own.corners, true, true);
|
|
1483
|
+
}
|
|
1479
1484
|
ctx.clip();
|
|
1480
1485
|
}
|
|
1481
1486
|
fillShadow(ctx, s, color, (dx) => {
|
|
1482
|
-
shadowShape(ctx,
|
|
1483
|
-
},
|
|
1487
|
+
shadowShape(ctx, x - dx, y, width, height, cut.corners);
|
|
1488
|
+
}, x + width + reach);
|
|
1484
1489
|
if (clipped)
|
|
1485
1490
|
ctx.restore();
|
|
1486
1491
|
}
|
|
@@ -1505,48 +1510,53 @@ function paintShadows(ctx, box, options, inset) {
|
|
|
1505
1510
|
furthest = Math.max(furthest, shadowReach(s.blur) + Math.abs(s.x) + Math.abs(s.y) + 1);
|
|
1506
1511
|
}
|
|
1507
1512
|
}
|
|
1508
|
-
const padCut = clampAround(options, pad, furthest + CLAMP_PAD);
|
|
1509
|
-
if (!padCut)
|
|
1510
|
-
return;
|
|
1511
|
-
Object.assign(pad, padCut);
|
|
1512
1513
|
const inner = insetCorners(corners, box.borderTop, box.borderRight, box.borderBottom, box.borderLeft);
|
|
1513
1514
|
for (let i = shadows.length - 1; i >= 0; i -= 1) {
|
|
1514
1515
|
const s = shadows[i];
|
|
1515
1516
|
if (!s.inset || (s.blur > 0 && !canShadow))
|
|
1516
1517
|
continue;
|
|
1517
1518
|
const color = inkColor(s.color, style.color);
|
|
1518
|
-
//
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1519
|
+
// The hole the shadow is cast around, moved and shrunk by the spread,
|
|
1520
|
+
// cut with the padding box: made from the cut one, it was a spread in
|
|
1521
|
+
// from the cut rather than from the box's edge, and a spread wider
|
|
1522
|
+
// than the margin left none.
|
|
1523
|
+
const [area, hole] = clampPair(options, furthest + CLAMP_PAD, { rect: pad, corners: inner }, {
|
|
1524
|
+
rect: {
|
|
1525
|
+
x: pad.x + s.x + s.spread,
|
|
1526
|
+
y: pad.y + s.y + s.spread,
|
|
1527
|
+
width: Math.max(0, pad.width - 2 * s.spread),
|
|
1528
|
+
height: Math.max(0, pad.height - 2 * s.spread),
|
|
1529
|
+
},
|
|
1530
|
+
corners: grownCorners(inner, -s.spread),
|
|
1531
|
+
});
|
|
1532
|
+
if (!area)
|
|
1533
|
+
continue;
|
|
1526
1534
|
if (!(s.blur > 0) && !s.x && !s.y && s.spread >= 0) {
|
|
1527
1535
|
// Tailwind's `ring-inset`: a band inside the padding edge
|
|
1528
1536
|
ctx.fillStyle = color;
|
|
1529
|
-
fillRing(ctx,
|
|
1537
|
+
fillRing(ctx, area.rect, area.corners, hole ? hole.rect : NOWHERE, hole ? hole.corners : SQUARE);
|
|
1530
1538
|
continue;
|
|
1531
1539
|
}
|
|
1532
1540
|
if (!ctx.clip || !ctx.save || !ctx.rect)
|
|
1533
1541
|
continue;
|
|
1542
|
+
const { x, y, width, height } = area.rect;
|
|
1534
1543
|
ctx.save();
|
|
1535
1544
|
ctx.beginPath();
|
|
1536
|
-
roundedRect(ctx,
|
|
1545
|
+
roundedRect(ctx, x, y, width, height, area.corners);
|
|
1537
1546
|
ctx.clip();
|
|
1538
1547
|
// a frame around the hole, as wide as the blur and the offset reach
|
|
1539
1548
|
const reach = shadowReach(s.blur) + Math.abs(s.x) + Math.abs(s.y) + 1;
|
|
1540
1549
|
const frame = {
|
|
1541
|
-
x:
|
|
1542
|
-
y:
|
|
1543
|
-
width:
|
|
1544
|
-
height:
|
|
1550
|
+
x: x - reach,
|
|
1551
|
+
y: y - reach,
|
|
1552
|
+
width: width + 2 * reach,
|
|
1553
|
+
height: height + 2 * reach,
|
|
1545
1554
|
};
|
|
1546
1555
|
fillShadow(ctx, s, color, (dx) => {
|
|
1547
1556
|
ctx.rect(frame.x - dx, frame.y, frame.width, frame.height);
|
|
1548
|
-
if (hole
|
|
1549
|
-
|
|
1557
|
+
if (hole) {
|
|
1558
|
+
const { rect: r } = hole;
|
|
1559
|
+
shadowShape(ctx, r.x - dx, r.y, r.width, r.height, hole.corners);
|
|
1550
1560
|
}
|
|
1551
1561
|
}, frame.x + frame.width + shadowReach(s.blur), 'evenodd');
|
|
1552
1562
|
ctx.restore();
|
|
@@ -2778,22 +2788,78 @@ function clampRect(options, x, y, w, h) {
|
|
|
2778
2788
|
return null;
|
|
2779
2789
|
return { x: x1, y: y1, w: x2 - x1, h: y2 - y1 };
|
|
2780
2790
|
}
|
|
2791
|
+
/**
|
|
2792
|
+
* `clampRect` for a rectangle that may be rounded, with the corners it is
|
|
2793
|
+
* left with. Where no radius is larger than `CLAMP_PAD` they are its own,
|
|
2794
|
+
* and it is cut as a square one is: a corner the cut leaves on a cut edge
|
|
2795
|
+
* curves no further in from it than the margin, which keeps it out of what
|
|
2796
|
+
* is painted. A larger radius, a circle's or a pill's, would bend into it
|
|
2797
|
+
* — a circle 400 across repainted a strip at a time was another shape in
|
|
2798
|
+
* every strip across its curve — so there the window is moved out past
|
|
2799
|
+
* any curve it would cross, and a corner on a side it cut is square
|
|
2800
|
+
* (`cutRounded`, as a rounded border's ring is cut).
|
|
2801
|
+
*/
|
|
2802
|
+
function clampArea(options, x, y, w, h, corners) {
|
|
2803
|
+
if (corners && curvesPast(corners, CLAMP_PAD)) {
|
|
2804
|
+
const shape = { rect: { x, y, width: w, height: h }, corners };
|
|
2805
|
+
const cut = cutRounded(roundedWindow(options, [shape]), shape);
|
|
2806
|
+
if (!cut)
|
|
2807
|
+
return null;
|
|
2808
|
+
const { rect } = cut;
|
|
2809
|
+
return {
|
|
2810
|
+
x: rect.x,
|
|
2811
|
+
y: rect.y,
|
|
2812
|
+
w: rect.width,
|
|
2813
|
+
h: rect.height,
|
|
2814
|
+
corners: cut.corners,
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
const rect = clampRect(options, x, y, w, h);
|
|
2818
|
+
return rect && { x: rect.x, y: rect.y, w: rect.w, h: rect.h, corners };
|
|
2819
|
+
}
|
|
2820
|
+
/**
|
|
2821
|
+
* Two rounded rectangles drawn against each other — a shadow's shape and
|
|
2822
|
+
* the box it is clipped out of, a hole and what it is cut in — cut to one
|
|
2823
|
+
* window, `pad` round what the paint reaches, as `clampArea` cuts one:
|
|
2824
|
+
* where neither has a corner that curves further than `CLAMP_PAD`, as
|
|
2825
|
+
* square ones are, keeping their corners, and otherwise to a window moved
|
|
2826
|
+
* out past every curve of either it would cross, each square on the sides
|
|
2827
|
+
* it cut. `CLAMP_PAD` and not `pad`, because what a shadow's margin has
|
|
2828
|
+
* past it is what the blur casts back across the cut, and a corner bent
|
|
2829
|
+
* into the margin is cast that far further in. Null for one with nothing
|
|
2830
|
+
* in the window.
|
|
2831
|
+
*/
|
|
2832
|
+
function clampPair(options, pad, a, b) {
|
|
2833
|
+
if (!curvesPast(a.corners, CLAMP_PAD) && !curvesPast(b.corners, CLAMP_PAD)) {
|
|
2834
|
+
return [clampKeeping(options, a, pad), clampKeeping(options, b, pad)];
|
|
2835
|
+
}
|
|
2836
|
+
const cut = roundedWindow(options, [a, b], pad);
|
|
2837
|
+
return [cutRounded(cut, a), cutRounded(cut, b)];
|
|
2838
|
+
}
|
|
2839
|
+
/** `clampAround` for a rounded rectangle, keeping its corners. */
|
|
2840
|
+
function clampKeeping(options, { rect, corners }, pad) {
|
|
2841
|
+
const cut = clampAround(options, rect, pad);
|
|
2842
|
+
return cut && { rect: cut, corners };
|
|
2843
|
+
}
|
|
2844
|
+
/** Whether a corner curves further than `by` along either of its sides. */
|
|
2845
|
+
function curvesPast({ x, y }, by) {
|
|
2846
|
+
for (let i = 0; i < 4; i += 1)
|
|
2847
|
+
if (x[i] > by || y[i] > by)
|
|
2848
|
+
return true;
|
|
2849
|
+
return false;
|
|
2850
|
+
}
|
|
2781
2851
|
function paintBackground(ctx, box, options, style) {
|
|
2782
2852
|
const color = style.backgroundColor;
|
|
2783
2853
|
const gradient = style.backgroundGradient;
|
|
2784
2854
|
const solid = !isTransparent(color);
|
|
2785
2855
|
if (!solid && !gradient)
|
|
2786
2856
|
return;
|
|
2787
|
-
const
|
|
2788
|
-
const rect = area && clampRect(options, area.x, area.y, area.w, area.h);
|
|
2857
|
+
const rect = clipArea(box, options, style, !!(ctx.roundRect && ctx.fill && ctx.beginPath));
|
|
2789
2858
|
if (!rect)
|
|
2790
2859
|
return;
|
|
2791
|
-
const rounded =
|
|
2860
|
+
const rounded = rect.corners;
|
|
2792
2861
|
const fill = () => {
|
|
2793
2862
|
if (rounded) {
|
|
2794
|
-
// The clamp can only have cut edges further than CLAMP_PAD outside
|
|
2795
|
-
// the damage, and a sane radius is smaller than that — so a corner
|
|
2796
|
-
// that survives the cut is whole, and a cut edge is offscreen.
|
|
2797
2863
|
ctx.beginPath();
|
|
2798
2864
|
roundedRect(ctx, rect.x, rect.y, rect.w, rect.h, rounded);
|
|
2799
2865
|
ctx.fill();
|
|
@@ -3521,7 +3587,7 @@ function roundedTile(size, [modeX, modeY], [w, h], at) {
|
|
|
3521
3587
|
}
|
|
3522
3588
|
/**
|
|
3523
3589
|
* A background tile's size (CSS Backgrounds 3, 3.9, over CSS Images' sizing
|
|
3524
|
-
* of an object): `auto` is the image's own, as `
|
|
3590
|
+
* of an object): `auto` is the image's own, as `concreteSize` has it; `cover`
|
|
3525
3591
|
* and `contain` scale it to fill the positioning area or to fit inside it,
|
|
3526
3592
|
* keeping its ratio, and one with no ratio is the size of the area; and a
|
|
3527
3593
|
* width or a height alone takes the other from the ratio, or else from the
|
|
@@ -3530,7 +3596,7 @@ function roundedTile(size, [modeX, modeY], [w, h], at) {
|
|
|
3530
3596
|
*/
|
|
3531
3597
|
function sizedTile(size, image, area, scale) {
|
|
3532
3598
|
if (size === 'auto')
|
|
3533
|
-
return
|
|
3599
|
+
return concreteSize(image, area.width, area.height, scale);
|
|
3534
3600
|
const { ratio } = image;
|
|
3535
3601
|
if (size === 'cover' || size === 'contain') {
|
|
3536
3602
|
if (!(ratio > 0 && area.height > 0))
|
|
@@ -3555,32 +3621,7 @@ function sizedTile(size, image, area, scale) {
|
|
|
3555
3621
|
return [h * ratio, h];
|
|
3556
3622
|
return [image.width === null ? area.width : image.width * scale, h];
|
|
3557
3623
|
}
|
|
3558
|
-
return
|
|
3559
|
-
}
|
|
3560
|
-
/**
|
|
3561
|
-
* A background image's size, in device pixels, where nothing sets it — CSS
|
|
3562
|
-
* 2.1 has no `background-size` — which is CSS Images' default sizing: an
|
|
3563
|
-
* image's own size where it has one; the dimension it lacks from its ratio,
|
|
3564
|
-
* or else from the positioning area; and one with a ratio alone as large as
|
|
3565
|
-
* fits in the area. An SVG may be any of these, and a raster image is the
|
|
3566
|
-
* first.
|
|
3567
|
-
*/
|
|
3568
|
-
function tileSize(size, area, scale) {
|
|
3569
|
-
const { ratio } = size;
|
|
3570
|
-
const width = size.width === null ? null : size.width * scale;
|
|
3571
|
-
const height = size.height === null ? null : size.height * scale;
|
|
3572
|
-
if (width !== null && height !== null)
|
|
3573
|
-
return [width, height];
|
|
3574
|
-
if (width !== null)
|
|
3575
|
-
return [width, ratio > 0 ? width / ratio : area.height];
|
|
3576
|
-
if (height !== null)
|
|
3577
|
-
return [ratio > 0 ? height * ratio : area.width, height];
|
|
3578
|
-
if (ratio > 0) {
|
|
3579
|
-
return area.width / area.height > ratio
|
|
3580
|
-
? [area.height * ratio, area.height]
|
|
3581
|
-
: [area.width, area.width / ratio];
|
|
3582
|
-
}
|
|
3583
|
-
return [area.width, area.height];
|
|
3624
|
+
return concreteSize(image, area.width, area.height, scale);
|
|
3584
3625
|
}
|
|
3585
3626
|
/**
|
|
3586
3627
|
* A box's outline (CSS 2.1 18.4, CSS UI 4 5): a border of its own width,
|
|
@@ -3645,7 +3686,7 @@ function paintOutline(ctx, box, options) {
|
|
|
3645
3686
|
*
|
|
3646
3687
|
* Where two sides that meet differ in colour — one of them transparent
|
|
3647
3688
|
* included — the corner is cut on the diagonal from its outer point to its
|
|
3648
|
-
* inner one and each side takes its half (CSS Backgrounds 3, 4.
|
|
3689
|
+
* inner one and each side takes its half (CSS Backgrounds 3, 4.4), so a side
|
|
3649
3690
|
* is a trapezoid, and a triangle where the box inside the borders has no
|
|
3650
3691
|
* width: the CSS triangle a dropdown's caret and a tooltip's arrow are
|
|
3651
3692
|
* drawn with, which came out as the rectangle around it. Only a solid side
|
|
@@ -3754,7 +3795,7 @@ function paintBorders(ctx, box, options) {
|
|
|
3754
3795
|
}
|
|
3755
3796
|
/**
|
|
3756
3797
|
* Whether the corner two sides meet at is cut on its diagonal, each side
|
|
3757
|
-
* taking its half (CSS Backgrounds 3, 4.
|
|
3798
|
+
* taking its half (CSS Backgrounds 3, 4.4): where both have a width, they
|
|
3758
3799
|
* are painted in different colours or only one is painted at all, and each
|
|
3759
3800
|
* is solid or unpainted. Sides of one colour make the same corner either
|
|
3760
3801
|
* way, and so does a corner of a single pixel, which is left to the side
|
|
@@ -3767,12 +3808,14 @@ function mitred(a, b, inkA, inkB, styleA, styleB) {
|
|
|
3767
3808
|
(inkB === null || styleB === 'solid'));
|
|
3768
3809
|
}
|
|
3769
3810
|
/**
|
|
3770
|
-
* Fill a
|
|
3771
|
-
*
|
|
3772
|
-
*
|
|
3773
|
-
*
|
|
3774
|
-
*
|
|
3775
|
-
*
|
|
3811
|
+
* Fill a polygon, its corners as `x, y` pairs, cut to `area`: the part of
|
|
3812
|
+
* it near what is painted, which is what keeps a side thousands of pixels
|
|
3813
|
+
* long inside X's coordinates. Cut edge by edge rather than by moving its
|
|
3814
|
+
* corners in, which would turn a diagonal that crosses the painted area: a
|
|
3815
|
+
* slanted divider, `border-left: 100vw solid transparent`, has a corner far
|
|
3816
|
+
* outside it. A polygon that is not convex, a rounded ring's share, may
|
|
3817
|
+
* come out of the cut with an edge run there and back along the area's
|
|
3818
|
+
* border, which fills nothing, and that border is outside the paint.
|
|
3776
3819
|
*/
|
|
3777
3820
|
function fillPolygon(ctx, points, area) {
|
|
3778
3821
|
const bounds = [area.x, area.y, area.x + area.w, area.y + area.h];
|
|
@@ -3843,9 +3886,7 @@ function paintBorderImage(ctx, box, options) {
|
|
|
3843
3886
|
// the image's size, CSS Images' default sizing in the area: a drawing
|
|
3844
3887
|
// with no size of its own is as large as it fits there, and a gradient,
|
|
3845
3888
|
// which has none, is the area's
|
|
3846
|
-
const [cw, ch] = loaded
|
|
3847
|
-
? tileSize(loaded, { x: 0, y: 0, width: aw, height: ah }, scale)
|
|
3848
|
-
: [aw, ah];
|
|
3889
|
+
const [cw, ch] = loaded ? concreteSize(loaded, aw, ah, scale) : [aw, ah];
|
|
3849
3890
|
const iw = cw / scale;
|
|
3850
3891
|
const ih = ch / scale;
|
|
3851
3892
|
if (!(iw > 0 && ih > 0))
|
|
@@ -4032,17 +4073,18 @@ function edgeRun(mode, start, length, tile) {
|
|
|
4032
4073
|
}
|
|
4033
4074
|
/**
|
|
4034
4075
|
* A rounded box's border as the ring between its border edge and its
|
|
4035
|
-
* padding edge, each rounded, where every side that has a border has
|
|
4036
|
-
*
|
|
4037
|
-
*
|
|
4038
|
-
*
|
|
4039
|
-
*
|
|
4040
|
-
*
|
|
4041
|
-
*
|
|
4076
|
+
* padding edge, each rounded, where every side that has a border has a
|
|
4077
|
+
* solid rule: a card's or a button's. Drawn straight, its corners were
|
|
4078
|
+
* square over the background's rounded ones, and an accent border down one
|
|
4079
|
+
* side did not follow the corner. The inner radius is the outer less the
|
|
4080
|
+
* wider border at that corner (CSS Backgrounds 3, 5.2). Sides of one colour
|
|
4081
|
+
* are one fill; sides of more are each given their share of the ring
|
|
4082
|
+
* (`ringSides`). False where the border is not such a one, and it is drawn
|
|
4083
|
+
* a side at a time.
|
|
4042
4084
|
*/
|
|
4043
4085
|
function roundedRing(ctx, box, x, y, w, h, options) {
|
|
4044
4086
|
const s = box.style;
|
|
4045
|
-
if (!ctx.
|
|
4087
|
+
if (!ctx.fill || !ctx.beginPath)
|
|
4046
4088
|
return false;
|
|
4047
4089
|
const corners = cornersOf(s, w, h);
|
|
4048
4090
|
if (!corners)
|
|
@@ -4054,23 +4096,335 @@ function roundedRing(ctx, box, x, y, w, h, options) {
|
|
|
4054
4096
|
[box.borderLeft, s.borderLeftColor, s.borderLeftStyle],
|
|
4055
4097
|
];
|
|
4056
4098
|
let color = null;
|
|
4099
|
+
let mixed = false;
|
|
4057
4100
|
for (const [width, ink, style] of sides) {
|
|
4058
4101
|
if (!width)
|
|
4059
4102
|
continue;
|
|
4060
|
-
if (style !== 'solid'
|
|
4103
|
+
if (style !== 'solid')
|
|
4061
4104
|
return false;
|
|
4105
|
+
if (color !== null && ink !== color)
|
|
4106
|
+
mixed = true;
|
|
4062
4107
|
color = ink;
|
|
4063
4108
|
}
|
|
4109
|
+
if (mixed)
|
|
4110
|
+
return ringSides(ctx, box, x, y, w, h, corners, options);
|
|
4111
|
+
if (!ctx.roundRect)
|
|
4112
|
+
return false;
|
|
4064
4113
|
if (color === null)
|
|
4065
4114
|
return true;
|
|
4066
4115
|
if (isTransparent(color))
|
|
4067
4116
|
return true;
|
|
4117
|
+
wholeRing(ctx, box, inkColor(color, s.color), x, y, w, h, corners, options);
|
|
4118
|
+
return true;
|
|
4119
|
+
}
|
|
4120
|
+
/** The ring of a rounded border in one colour, cut to what the paint
|
|
4121
|
+
* reaches (`cutRing`). */
|
|
4122
|
+
function wholeRing(ctx, box, color, x, y, w, h, corners, options) {
|
|
4068
4123
|
const ring = cutRing(options, x, y, w, h, corners, box.borderTop, box.borderRight, box.borderBottom, box.borderLeft);
|
|
4069
4124
|
if (!ring)
|
|
4070
|
-
return
|
|
4125
|
+
return;
|
|
4071
4126
|
const { outer, inner } = ring;
|
|
4072
|
-
ctx.fillStyle =
|
|
4073
|
-
fillRing(ctx, outer.rect, outer.corners, inner ? inner.rect :
|
|
4127
|
+
ctx.fillStyle = color;
|
|
4128
|
+
fillRing(ctx, outer.rect, outer.corners, inner ? inner.rect : NOWHERE, inner ? inner.corners : outer.corners);
|
|
4129
|
+
}
|
|
4130
|
+
/** How far a chord of a flattened corner may stray from its curve, in
|
|
4131
|
+
* device pixels: a sixteenth, well inside what antialiasing shows. */
|
|
4132
|
+
const CURVE_TOLERANCE = 1 / 16;
|
|
4133
|
+
/** How far past a cut a share reaches under the one drawn over it, in
|
|
4134
|
+
* device pixels: past the antialiased edge of either. */
|
|
4135
|
+
const SEAM = 1.5;
|
|
4136
|
+
/** Which way a border's sides run at each corner, clockwise from the top
|
|
4137
|
+
* left: the side that ends at corner i arrives along `ALONG[i]`, and the
|
|
4138
|
+
* side that begins there leaves along `ALONG[i + 1]`. */
|
|
4139
|
+
const ALONG = [
|
|
4140
|
+
[0, -1],
|
|
4141
|
+
[1, 0],
|
|
4142
|
+
[0, 1],
|
|
4143
|
+
[-1, 0],
|
|
4144
|
+
];
|
|
4145
|
+
/**
|
|
4146
|
+
* A rounded border whose solid sides are not all one colour, each side's
|
|
4147
|
+
* share of the ring filled in its own. Where two sides of different colours
|
|
4148
|
+
* meet, the corner is cut on the line from the border box's corner through
|
|
4149
|
+
* the padding box's, as Blink cuts it: CSS Backgrounds 3, 4.4 puts the
|
|
4150
|
+
* transition on the curve at a point that follows the ratio of the two
|
|
4151
|
+
* widths and leaves the rest to the UA. A corner between a side and one
|
|
4152
|
+
* with no width is all the side's (4.4 again), and sides of one colour that
|
|
4153
|
+
* meet are one fill, so nothing is cut where the colour does not change.
|
|
4154
|
+
* Drawn a side at a time, the spinner the web draws — `border-radius: 50%`
|
|
4155
|
+
* with one side of another colour — was a square frame.
|
|
4156
|
+
*
|
|
4157
|
+
* A share is a polygon: the curves flattened to chords no further than
|
|
4158
|
+
* `CURVE_TOLERANCE` from them, so that it is cut to what the paint reaches
|
|
4159
|
+
* as the square corners are (`fillPolygon`) and a large ring repainted a
|
|
4160
|
+
* strip at a time is the same ring. False where the context draws no
|
|
4161
|
+
* lines, and the sides are drawn straight.
|
|
4162
|
+
*/
|
|
4163
|
+
function ringSides(ctx, box, x, y, w, h, corners, options) {
|
|
4164
|
+
if (!ctx.moveTo || !ctx.lineTo)
|
|
4165
|
+
return false;
|
|
4166
|
+
const area = clampRect(options, x, y, w, h);
|
|
4167
|
+
if (!area)
|
|
4168
|
+
return true;
|
|
4169
|
+
const s = box.style;
|
|
4170
|
+
const widths = [
|
|
4171
|
+
box.borderTop,
|
|
4172
|
+
box.borderRight,
|
|
4173
|
+
box.borderBottom,
|
|
4174
|
+
box.borderLeft,
|
|
4175
|
+
];
|
|
4176
|
+
const colors = [
|
|
4177
|
+
s.borderTopColor,
|
|
4178
|
+
s.borderRightColor,
|
|
4179
|
+
s.borderBottomColor,
|
|
4180
|
+
s.borderLeftColor,
|
|
4181
|
+
];
|
|
4182
|
+
// what each side is painted in, null where it paints nothing
|
|
4183
|
+
const inks = widths.map((width, i) => width > 0 && !isTransparent(colors[i])
|
|
4184
|
+
? inkColor(colors[i], s.color)
|
|
4185
|
+
: null);
|
|
4186
|
+
// Corners from the top left, clockwise. Corner i is where side i - 1
|
|
4187
|
+
// ends and side i begins: the top left ends the left and begins the top.
|
|
4188
|
+
// Two sides of one colour are joined across their corner.
|
|
4189
|
+
const joined = [0, 1, 2, 3].map((i) => {
|
|
4190
|
+
const before = (i + 3) % 4;
|
|
4191
|
+
return widths[before] > 0 && widths[i] > 0 && inks[before] === inks[i];
|
|
4192
|
+
});
|
|
4193
|
+
if (!joined.includes(false)) {
|
|
4194
|
+
// one colour after all, `currentColor` and the colour it is
|
|
4195
|
+
if (!ctx.roundRect)
|
|
4196
|
+
return false;
|
|
4197
|
+
if (inks[0] !== null) {
|
|
4198
|
+
wholeRing(ctx, box, inks[0], x, y, w, h, corners, options);
|
|
4199
|
+
}
|
|
4200
|
+
return true;
|
|
4201
|
+
}
|
|
4202
|
+
const [t, r, b, l] = widths;
|
|
4203
|
+
const x1 = x + w;
|
|
4204
|
+
const y1 = y + h;
|
|
4205
|
+
// the padding box, inside the borders
|
|
4206
|
+
const ix0 = x + l;
|
|
4207
|
+
const iy0 = y + t;
|
|
4208
|
+
const ix1 = Math.max(ix0, x1 - r);
|
|
4209
|
+
const iy1 = Math.max(iy0, y1 - b);
|
|
4210
|
+
const inside = insetCorners(corners, t, r, b, l);
|
|
4211
|
+
const hollow = ix1 > ix0 && iy1 > iy0;
|
|
4212
|
+
// each corner's point on the border box and on the padding box, which
|
|
4213
|
+
// way it points, and the quarter ellipses that round the two there
|
|
4214
|
+
const sx = [-1, 1, 1, -1];
|
|
4215
|
+
const sy = [-1, -1, 1, 1];
|
|
4216
|
+
const ox = [x, x1, x1, x];
|
|
4217
|
+
const oy = [y, y, y1, y1];
|
|
4218
|
+
const px = [ix0, ix1, ix1, ix0];
|
|
4219
|
+
const py = [iy0, iy0, iy1, iy1];
|
|
4220
|
+
const quarter = (i, cornerX, cornerY, rx, ry) => rx > 0 && ry > 0
|
|
4221
|
+
? { cx: cornerX - sx[i] * rx, cy: cornerY - sy[i] * ry, rx, ry }
|
|
4222
|
+
: null;
|
|
4223
|
+
const outer = [];
|
|
4224
|
+
const inner = [];
|
|
4225
|
+
for (let i = 0; i < 4; i += 1) {
|
|
4226
|
+
outer.push(quarter(i, ox[i], oy[i], corners.x[i], corners.y[i]));
|
|
4227
|
+
inner.push(hollow ? quarter(i, px[i], py[i], inside.x[i], inside.y[i]) : null);
|
|
4228
|
+
}
|
|
4229
|
+
// A corner's quarter turns clockwise from the angle it starts at: the top
|
|
4230
|
+
// left's from pointing left to pointing up.
|
|
4231
|
+
const QUARTER = Math.PI / 2;
|
|
4232
|
+
const start = (i) => Math.PI + i * QUARTER;
|
|
4233
|
+
/** Where a line from (fromX, fromY) along (dx, dy) first meets a quarter
|
|
4234
|
+
* ellipse, as the angle it is at; the line starts outside it. */
|
|
4235
|
+
const crossing = (i, q, fromX, fromY, dx, dy) => {
|
|
4236
|
+
const a0 = start(i);
|
|
4237
|
+
if (!q)
|
|
4238
|
+
return a0;
|
|
4239
|
+
const ux = (fromX - q.cx) / q.rx;
|
|
4240
|
+
const uy = (fromY - q.cy) / q.ry;
|
|
4241
|
+
const vx = dx / q.rx;
|
|
4242
|
+
const vy = dy / q.ry;
|
|
4243
|
+
const qa = vx * vx + vy * vy;
|
|
4244
|
+
const qb = ux * vx + uy * vy;
|
|
4245
|
+
const disc = qb * qb - qa * (ux * ux + uy * uy - 1);
|
|
4246
|
+
const k = qa > 0 && disc > 0 ? (-qb - Math.sqrt(disc)) / qa : 0;
|
|
4247
|
+
let a = Math.atan2(uy + k * vy, ux + k * vx);
|
|
4248
|
+
// into the quarter's own turn
|
|
4249
|
+
a += 2 * Math.PI * Math.round((a0 + QUARTER / 2 - a) / (2 * Math.PI));
|
|
4250
|
+
return Math.min(a0 + QUARTER, Math.max(a0, a));
|
|
4251
|
+
};
|
|
4252
|
+
// Where the shares meet at each corner, as angles on its two quarters: on
|
|
4253
|
+
// the cut, or at the end of the corner where one side has no width and
|
|
4254
|
+
// the other has all of it — the side that has none paints nothing.
|
|
4255
|
+
const cutOuter = [];
|
|
4256
|
+
const cutInner = [];
|
|
4257
|
+
for (let i = 0; i < 4; i += 1) {
|
|
4258
|
+
const before = widths[(i + 3) % 4] > 0;
|
|
4259
|
+
const after = widths[i] > 0;
|
|
4260
|
+
if (before && after) {
|
|
4261
|
+
const dx = px[i] - ox[i];
|
|
4262
|
+
const dy = py[i] - oy[i];
|
|
4263
|
+
cutOuter.push(crossing(i, outer[i], ox[i], oy[i], dx, dy));
|
|
4264
|
+
cutInner.push(crossing(i, inner[i], px[i], py[i], dx, dy));
|
|
4265
|
+
}
|
|
4266
|
+
else {
|
|
4267
|
+
const end = before ? start(i) + QUARTER : start(i);
|
|
4268
|
+
cutOuter.push(end);
|
|
4269
|
+
cutInner.push(end);
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
// The shares, a run of sides of one colour each, from the corner the
|
|
4273
|
+
// colour changes at to the next one it changes at. Those that show what
|
|
4274
|
+
// is under them are drawn first.
|
|
4275
|
+
const runs = [];
|
|
4276
|
+
for (let i = 0; i < 4; i += 1) {
|
|
4277
|
+
if (joined[i])
|
|
4278
|
+
continue;
|
|
4279
|
+
let j = (i + 1) % 4;
|
|
4280
|
+
while (joined[j])
|
|
4281
|
+
j = (j + 1) % 4;
|
|
4282
|
+
runs.push({ from: i, to: j, ink: inks[i] });
|
|
4283
|
+
}
|
|
4284
|
+
const opaque = (ink) => ink !== null && alphaOf(ink) === 1;
|
|
4285
|
+
runs.sort((a, b) => Number(opaque(a.ink)) - Number(opaque(b.ink)));
|
|
4286
|
+
/** Whether a share reaches past its cut at a corner, under the one on
|
|
4287
|
+
* the other side of it: where that one is drawn after it and covers it. */
|
|
4288
|
+
const under = (k, corner) => {
|
|
4289
|
+
const before = widths[(corner + 3) % 4] > 0;
|
|
4290
|
+
if (!(before && widths[corner] > 0))
|
|
4291
|
+
return false;
|
|
4292
|
+
const other = runs.findIndex((run) => run !== runs[k] && (run.from === corner || run.to === corner));
|
|
4293
|
+
return other > k && opaque(runs[other].ink);
|
|
4294
|
+
};
|
|
4295
|
+
// The straight part of each side's edge, between the quarters at its
|
|
4296
|
+
// ends, on the border edge and the padding edge: as far as a share may
|
|
4297
|
+
// reach along it past a corner.
|
|
4298
|
+
const straight = (q, across, down) => {
|
|
4299
|
+
const rx = (i) => q[i]?.rx ?? 0;
|
|
4300
|
+
const ry = (i) => q[i]?.ry ?? 0;
|
|
4301
|
+
return [
|
|
4302
|
+
across - rx(0) - rx(1),
|
|
4303
|
+
down - ry(1) - ry(2),
|
|
4304
|
+
across - rx(2) - rx(3),
|
|
4305
|
+
down - ry(3) - ry(0),
|
|
4306
|
+
].map((v) => Math.max(0, v));
|
|
4307
|
+
};
|
|
4308
|
+
const outerRoom = straight(outer, w, h);
|
|
4309
|
+
const innerRoom = straight(inner, ix1 - ix0, iy1 - iy0);
|
|
4310
|
+
/**
|
|
4311
|
+
* A share's end moved past the cut at a corner, onto the other share,
|
|
4312
|
+
* along an edge from the angle `a` it is at on a quarter: `forward` the
|
|
4313
|
+
* way the corner turns, onto the side that begins there, or back onto
|
|
4314
|
+
* the side that ends there. Far enough that the line it ends on is
|
|
4315
|
+
* `SEAM` from the cut, whatever angle the two make; as the angle it
|
|
4316
|
+
* reaches on the quarter and how far it goes on along the straight edge
|
|
4317
|
+
* past it, no further than `room`.
|
|
4318
|
+
*/
|
|
4319
|
+
const past = (q, i, a, forward, room) => {
|
|
4320
|
+
const end = forward ? start(i) + QUARTER : start(i);
|
|
4321
|
+
const sign = forward ? 1 : -1;
|
|
4322
|
+
const [ex, ey] = forward ? ALONG[(i + 1) % 4] : ALONG[i];
|
|
4323
|
+
// the way it goes, and the sine of its angle with the cut
|
|
4324
|
+
let tx = forward ? ex : -ex;
|
|
4325
|
+
let ty = forward ? ey : -ey;
|
|
4326
|
+
if (q && a !== end) {
|
|
4327
|
+
const speed = Math.hypot(q.rx * Math.sin(a), q.ry * Math.cos(a));
|
|
4328
|
+
tx = (sign * -q.rx * Math.sin(a)) / speed;
|
|
4329
|
+
ty = (sign * q.ry * Math.cos(a)) / speed;
|
|
4330
|
+
}
|
|
4331
|
+
const dx = px[i] - ox[i];
|
|
4332
|
+
const dy = py[i] - oy[i];
|
|
4333
|
+
const sine = Math.abs(tx * dy - ty * dx) / Math.hypot(dx, dy);
|
|
4334
|
+
let left = SEAM / Math.max(0.25, sine);
|
|
4335
|
+
let at = a;
|
|
4336
|
+
for (let n = 0; q && n < 8 && left > 0 && at !== end; n += 1) {
|
|
4337
|
+
const speed = Math.hypot(q.rx * Math.sin(at), q.ry * Math.cos(at));
|
|
4338
|
+
const turn = Math.abs(end - at);
|
|
4339
|
+
if (left >= turn * speed) {
|
|
4340
|
+
left -= turn * speed;
|
|
4341
|
+
at = end;
|
|
4342
|
+
}
|
|
4343
|
+
else {
|
|
4344
|
+
at += (sign * left) / speed;
|
|
4345
|
+
left = 0;
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
return [at, at === end ? Math.min(left, room) : 0];
|
|
4349
|
+
};
|
|
4350
|
+
const points = [];
|
|
4351
|
+
/** The point at an angle on a quarter, or the corner where it is square,
|
|
4352
|
+
* and `over` on past the quarter's end along the edge `along`. */
|
|
4353
|
+
const point = (q, a, cx, cy, over = 0, along = ALONG[0]) => {
|
|
4354
|
+
const x = (q ? q.cx + q.rx * Math.cos(a) : cx) + over * along[0];
|
|
4355
|
+
const y = (q ? q.cy + q.ry * Math.sin(a) : cy) + over * along[1];
|
|
4356
|
+
const n = points.length;
|
|
4357
|
+
if (n && points[n - 2] === x && points[n - 1] === y)
|
|
4358
|
+
return;
|
|
4359
|
+
points.push(x, y);
|
|
4360
|
+
};
|
|
4361
|
+
/** A quarter's curve from one angle to another, as chords, after the
|
|
4362
|
+
* point it starts at. */
|
|
4363
|
+
const sweep = (q, from, to) => {
|
|
4364
|
+
if (!q || from === to)
|
|
4365
|
+
return;
|
|
4366
|
+
const radius = Math.max(q.rx, q.ry);
|
|
4367
|
+
const step = radius > CURVE_TOLERANCE
|
|
4368
|
+
? 2 * Math.acos(1 - CURVE_TOLERANCE / radius)
|
|
4369
|
+
: QUARTER;
|
|
4370
|
+
const n = Math.min(64, Math.ceil(Math.abs(to - from) / step));
|
|
4371
|
+
for (let k = 1; k <= n; k += 1) {
|
|
4372
|
+
point(q, from + ((to - from) * k) / n, 0, 0);
|
|
4373
|
+
}
|
|
4374
|
+
};
|
|
4375
|
+
for (let k = 0; k < runs.length; k += 1) {
|
|
4376
|
+
const { from: i, to: j, ink } = runs[k];
|
|
4377
|
+
if (ink === null)
|
|
4378
|
+
continue;
|
|
4379
|
+
// Where the share starts and ends on each edge. Two antialiased fills
|
|
4380
|
+
// that meet on a line each cover half of the pixels along it, and what
|
|
4381
|
+
// is under the border shows through between them. So a share reaches
|
|
4382
|
+
// on under the one drawn after it and covering it, and that one's
|
|
4383
|
+
// edge is the cut, over it — as Blink overdraws a corner, and as the
|
|
4384
|
+
// square corners are drawn.
|
|
4385
|
+
let [so, soOver] = [cutOuter[i], 0];
|
|
4386
|
+
let [si, siOver] = [cutInner[i], 0];
|
|
4387
|
+
let [eo, eoOver] = [cutOuter[j], 0];
|
|
4388
|
+
let [ei, eiOver] = [cutInner[j], 0];
|
|
4389
|
+
const back = (i + 3) % 4;
|
|
4390
|
+
if (under(k, i)) {
|
|
4391
|
+
[so, soOver] = past(outer[i], i, so, false, outerRoom[back]);
|
|
4392
|
+
[si, siOver] = past(inner[i], i, si, false, innerRoom[back]);
|
|
4393
|
+
}
|
|
4394
|
+
if (under(k, j)) {
|
|
4395
|
+
[eo, eoOver] = past(outer[j], j, eo, true, outerRoom[j]);
|
|
4396
|
+
[ei, eiOver] = past(inner[j], j, ei, true, innerRoom[j]);
|
|
4397
|
+
}
|
|
4398
|
+
const onward = ALONG[(j + 1) % 4];
|
|
4399
|
+
const backward = [-ALONG[i][0], -ALONG[i][1]];
|
|
4400
|
+
points.length = 0;
|
|
4401
|
+
// along the border edge, clockwise
|
|
4402
|
+
point(outer[i], so, ox[i], oy[i], soOver, backward);
|
|
4403
|
+
point(outer[i], so, ox[i], oy[i]);
|
|
4404
|
+
sweep(outer[i], so, start(i) + QUARTER);
|
|
4405
|
+
for (let c = (i + 1) % 4;; c = (c + 1) % 4) {
|
|
4406
|
+
point(outer[c], start(c), ox[c], oy[c]);
|
|
4407
|
+
if (c === j)
|
|
4408
|
+
break;
|
|
4409
|
+
sweep(outer[c], start(c), start(c) + QUARTER);
|
|
4410
|
+
}
|
|
4411
|
+
sweep(outer[j], start(j), eo);
|
|
4412
|
+
point(outer[j], eo, ox[j], oy[j], eoOver, onward);
|
|
4413
|
+
// and back along the padding edge
|
|
4414
|
+
point(inner[j], ei, px[j], py[j], eiOver, onward);
|
|
4415
|
+
point(inner[j], ei, px[j], py[j]);
|
|
4416
|
+
sweep(inner[j], ei, start(j));
|
|
4417
|
+
for (let c = (j + 3) % 4;; c = (c + 3) % 4) {
|
|
4418
|
+
point(inner[c], start(c) + QUARTER, px[c], py[c]);
|
|
4419
|
+
if (c === i)
|
|
4420
|
+
break;
|
|
4421
|
+
sweep(inner[c], start(c) + QUARTER, start(c));
|
|
4422
|
+
}
|
|
4423
|
+
sweep(inner[i], start(i) + QUARTER, si);
|
|
4424
|
+
point(inner[i], si, px[i], py[i], siOver, backward);
|
|
4425
|
+
ctx.fillStyle = ink;
|
|
4426
|
+
fillPolygon(ctx, points, area);
|
|
4427
|
+
}
|
|
4074
4428
|
return true;
|
|
4075
4429
|
}
|
|
4076
4430
|
/**
|
|
@@ -4081,68 +4435,82 @@ function roundedRing(ctx, box, x, y, w, h, options) {
|
|
|
4081
4435
|
* the box first and insetting the cut by the borders put the hole a
|
|
4082
4436
|
* border's width in from the cut rather than from the box's edge, and left
|
|
4083
4437
|
* none where a border was wider than `CLAMP_PAD`: the ring filled whatever
|
|
4084
|
-
* of the box a paint reached.
|
|
4085
|
-
*
|
|
4086
|
-
*
|
|
4087
|
-
*
|
|
4438
|
+
* of the box a paint reached. The window is the border edge's
|
|
4439
|
+
* (`roundedWindow`), whose curves the padding edge's run inside, and each
|
|
4440
|
+
* edge is cut to it as `cutRounded` cuts one. Null where the ring has
|
|
4441
|
+
* nothing in the window.
|
|
4088
4442
|
*/
|
|
4089
4443
|
function cutRing(options, x, y, w, h, corners, top, right, bottom, left) {
|
|
4090
|
-
const {
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
const across = [
|
|
4094
|
-
[x, x + cx[0]],
|
|
4095
|
-
[x + w - cx[1], x + w],
|
|
4096
|
-
[x + w - cx[2], x + w],
|
|
4097
|
-
[x, x + cx[3]],
|
|
4098
|
-
];
|
|
4099
|
-
const down = [
|
|
4100
|
-
[y, y + cy[0]],
|
|
4101
|
-
[y, y + cy[1]],
|
|
4102
|
-
[y + h - cy[2], y + h],
|
|
4103
|
-
[y + h - cy[3], y + h],
|
|
4104
|
-
];
|
|
4105
|
-
const d = options.damage;
|
|
4106
|
-
const side = (at, spans, towards) => Math.max(-COORD_LIMIT, Math.min(COORD_LIMIT, outOfSpans(at, spans, towards)));
|
|
4107
|
-
const x0 = side(d ? d.x - CLAMP_PAD : -COORD_LIMIT, across, -1);
|
|
4108
|
-
const y0 = side(d ? d.y - CLAMP_PAD : -COORD_LIMIT, down, -1);
|
|
4109
|
-
const x1 = side(d ? d.x + d.width + CLAMP_PAD : COORD_LIMIT, across, 1);
|
|
4110
|
-
const y1 = side(d ? d.y + d.height + CLAMP_PAD : COORD_LIMIT, down, 1);
|
|
4111
|
-
const cut = (rx, ry, rw, rh, c) => {
|
|
4112
|
-
const l = Math.max(rx, x0);
|
|
4113
|
-
const t = Math.max(ry, y0);
|
|
4114
|
-
const r = Math.min(rx + rw, x1);
|
|
4115
|
-
const b = Math.min(ry + rh, y1);
|
|
4116
|
-
if (r <= l || b <= t)
|
|
4117
|
-
return null;
|
|
4118
|
-
// the corners of the sides the window left where they were
|
|
4119
|
-
const kept = [
|
|
4120
|
-
t === ry && l === rx,
|
|
4121
|
-
t === ry && r === rx + rw,
|
|
4122
|
-
b === ry + rh && r === rx + rw,
|
|
4123
|
-
b === ry + rh && l === rx,
|
|
4124
|
-
];
|
|
4125
|
-
return {
|
|
4126
|
-
rect: { x: l, y: t, width: r - l, height: b - t },
|
|
4127
|
-
corners: {
|
|
4128
|
-
x: c.x.map((v, i) => (kept[i] ? v : 0)),
|
|
4129
|
-
y: c.y.map((v, i) => (kept[i] ? v : 0)),
|
|
4130
|
-
},
|
|
4131
|
-
};
|
|
4132
|
-
};
|
|
4133
|
-
const outer = cut(x, y, w, h, corners);
|
|
4444
|
+
const edge = { rect: { x, y, width: w, height: h }, corners };
|
|
4445
|
+
const cut = roundedWindow(options, [edge]);
|
|
4446
|
+
const outer = cutRounded(cut, edge);
|
|
4134
4447
|
if (!outer)
|
|
4135
4448
|
return null;
|
|
4136
4449
|
const iw = w - left - right;
|
|
4137
4450
|
const ih = h - top - bottom;
|
|
4138
4451
|
const inner = iw > 0 && ih > 0
|
|
4139
|
-
? cut
|
|
4452
|
+
? cutRounded(cut, {
|
|
4453
|
+
rect: { x: x + left, y: y + top, width: iw, height: ih },
|
|
4454
|
+
corners: insetCorners(corners, top, right, bottom, left),
|
|
4455
|
+
})
|
|
4140
4456
|
: null;
|
|
4141
4457
|
// a window inside the padding edge, where the ring has nothing
|
|
4142
4458
|
if (inner && sameRounded(outer, inner))
|
|
4143
4459
|
return null;
|
|
4144
4460
|
return { outer, inner };
|
|
4145
4461
|
}
|
|
4462
|
+
/**
|
|
4463
|
+
* The window `clampRect` cuts to, `pad` round what the paint reaches, for
|
|
4464
|
+
* rectangles with rounded corners: a cut is straight, so a side of it is
|
|
4465
|
+
* moved out past any corner's curve it would cross, in any of them.
|
|
4466
|
+
*/
|
|
4467
|
+
function roundedWindow(options, shapes, pad = CLAMP_PAD) {
|
|
4468
|
+
// where each corner's curve runs, across and down
|
|
4469
|
+
const across = [];
|
|
4470
|
+
const down = [];
|
|
4471
|
+
for (const { rect, corners } of shapes) {
|
|
4472
|
+
const { x, y, width: w, height: h } = rect;
|
|
4473
|
+
const { x: cx, y: cy } = corners;
|
|
4474
|
+
across.push([x, x + cx[0]], [x + w - cx[1], x + w], [x + w - cx[2], x + w], [x, x + cx[3]]);
|
|
4475
|
+
down.push([y, y + cy[0]], [y, y + cy[1]], [y + h - cy[2], y + h], [y + h - cy[3], y + h]);
|
|
4476
|
+
}
|
|
4477
|
+
const d = options.damage;
|
|
4478
|
+
const side = (at, spans, towards) => Math.max(-COORD_LIMIT, Math.min(COORD_LIMIT, outOfSpans(at, spans, towards)));
|
|
4479
|
+
return [
|
|
4480
|
+
side(d ? d.x - pad : -COORD_LIMIT, across, -1),
|
|
4481
|
+
side(d ? d.y - pad : -COORD_LIMIT, down, -1),
|
|
4482
|
+
side(d ? d.x + d.width + pad : COORD_LIMIT, across, 1),
|
|
4483
|
+
side(d ? d.y + d.height + pad : COORD_LIMIT, down, 1),
|
|
4484
|
+
];
|
|
4485
|
+
}
|
|
4486
|
+
/**
|
|
4487
|
+
* A rounded rectangle cut to a window (`roundedWindow`), as `clampRect`
|
|
4488
|
+
* cuts a rectangle: a corner on a side the window cut is square, its
|
|
4489
|
+
* curve being outside, and the rest are whole. Null where none of it is in
|
|
4490
|
+
* the window.
|
|
4491
|
+
*/
|
|
4492
|
+
function cutRounded([x0, y0, x1, y1], { rect: { x, y, width: w, height: h }, corners: c }) {
|
|
4493
|
+
const l = Math.max(x, x0);
|
|
4494
|
+
const t = Math.max(y, y0);
|
|
4495
|
+
const r = Math.min(x + w, x1);
|
|
4496
|
+
const b = Math.min(y + h, y1);
|
|
4497
|
+
if (r <= l || b <= t)
|
|
4498
|
+
return null;
|
|
4499
|
+
// the corners of the sides the window left where they were
|
|
4500
|
+
const kept = [
|
|
4501
|
+
t === y && l === x,
|
|
4502
|
+
t === y && r === x + w,
|
|
4503
|
+
b === y + h && r === x + w,
|
|
4504
|
+
b === y + h && l === x,
|
|
4505
|
+
];
|
|
4506
|
+
return {
|
|
4507
|
+
rect: { x: l, y: t, width: r - l, height: b - t },
|
|
4508
|
+
corners: {
|
|
4509
|
+
x: c.x.map((v, i) => (kept[i] ? v : 0)),
|
|
4510
|
+
y: c.y.map((v, i) => (kept[i] ? v : 0)),
|
|
4511
|
+
},
|
|
4512
|
+
};
|
|
4513
|
+
}
|
|
4146
4514
|
/** A position moved out of any of `spans` it is inside, towards their
|
|
4147
4515
|
* starts where `towards` is negative and their ends where it is not. */
|
|
4148
4516
|
function outOfSpans(at, spans, towards) {
|
|
@@ -4419,11 +4787,10 @@ function paintCollapsedBorders(ctx, table, options) {
|
|
|
4419
4787
|
for (const s of segments) {
|
|
4420
4788
|
if (isTransparent(s.border.color))
|
|
4421
4789
|
continue;
|
|
4422
|
-
|
|
4423
|
-
if (!rect)
|
|
4790
|
+
if (!clampRect(options, s.x, s.y, s.w, s.h))
|
|
4424
4791
|
continue;
|
|
4425
4792
|
ctx.fillStyle = s.border.color;
|
|
4426
|
-
fillEdge(ctx,
|
|
4793
|
+
fillEdge(ctx, options, s.x, s.y, s.w, s.h, s.border.style, s.horizontal);
|
|
4427
4794
|
}
|
|
4428
4795
|
}
|
|
4429
4796
|
/**
|
|
@@ -4475,13 +4842,10 @@ function fillSide(ctx, options, at, x, y, w, h, widths, style, joins) {
|
|
|
4475
4842
|
}
|
|
4476
4843
|
const ex = at === 1 ? x + w - r : x;
|
|
4477
4844
|
const ey = at === 0 ? y : at === 2 ? y + h - b : y + t;
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
// not crawl as the viewport moves along a long edge.
|
|
4483
|
-
if (rect)
|
|
4484
|
-
fillEdge(ctx, rect, horizontal ? ex : ey, style, horizontal);
|
|
4845
|
+
if (horizontal)
|
|
4846
|
+
fillEdge(ctx, options, ex, ey, w, width, style, true);
|
|
4847
|
+
else
|
|
4848
|
+
fillEdge(ctx, options, ex, ey, width, h - t - b, style, false);
|
|
4485
4849
|
}
|
|
4486
4850
|
/**
|
|
4487
4851
|
* The corners of a box where two double sides of one colour meet, the top
|
|
@@ -4511,44 +4875,60 @@ const UNJOINED = [false, false, false, false];
|
|
|
4511
4875
|
function doubleBand(thickness) {
|
|
4512
4876
|
return Math.max(1, Math.floor(thickness / 3));
|
|
4513
4877
|
}
|
|
4514
|
-
|
|
4515
|
-
|
|
4878
|
+
/**
|
|
4879
|
+
* A side at `x, y`, `w` by `h`, running across when `horizontal`, as the
|
|
4880
|
+
* rectangles its style makes, each cut to what the paint reaches. The
|
|
4881
|
+
* dashes, the dots and a double side's lines are measured from the side
|
|
4882
|
+
* and cut after: measured from the cut, a side wider than `CLAMP_PAD` had
|
|
4883
|
+
* dashes as long and as far apart as the cut was wide, starting wherever
|
|
4884
|
+
* it started, and a strip repainted down one dash was striped across it.
|
|
4885
|
+
*/
|
|
4886
|
+
function fillEdge(ctx, options, x, y, w, h, style, horizontal) {
|
|
4516
4887
|
const length = horizontal ? w : h;
|
|
4517
4888
|
const thickness = horizontal ? h : w;
|
|
4518
|
-
if (length
|
|
4889
|
+
if (!(length > 0 && thickness > 0))
|
|
4519
4890
|
return;
|
|
4891
|
+
const fill = (fx, fy, fw, fh) => {
|
|
4892
|
+
const rect = clampRect(options, fx, fy, fw, fh);
|
|
4893
|
+
if (rect)
|
|
4894
|
+
ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
|
|
4895
|
+
};
|
|
4520
4896
|
if (style === 'dashed' || style === 'dotted') {
|
|
4897
|
+
// what of the side the paint reaches, so that a side far longer than
|
|
4898
|
+
// that is not walked from its start
|
|
4899
|
+
const cut = clampRect(options, x, y, w, h);
|
|
4900
|
+
if (!cut)
|
|
4901
|
+
return;
|
|
4521
4902
|
const period = style === 'dotted' ? thickness * 2 : thickness * 3;
|
|
4522
4903
|
const on = style === 'dotted' ? thickness : thickness * 2;
|
|
4523
|
-
const
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
continue;
|
|
4904
|
+
const start = horizontal ? x : y;
|
|
4905
|
+
const end = start + length;
|
|
4906
|
+
const from = horizontal ? cut.x : cut.y;
|
|
4907
|
+
const to = from + (horizontal ? cut.w : cut.h);
|
|
4908
|
+
// from the side's start, so the dash the paint cuts into is the one it
|
|
4909
|
+
// always was
|
|
4910
|
+
for (let i = start + Math.floor((from - start) / period) * period; i < to; i += period) {
|
|
4911
|
+
const run = Math.min(i + on, end) - i;
|
|
4532
4912
|
if (horizontal)
|
|
4533
|
-
|
|
4913
|
+
fill(i, y, run, thickness);
|
|
4534
4914
|
else
|
|
4535
|
-
|
|
4915
|
+
fill(x, i, thickness, run);
|
|
4536
4916
|
}
|
|
4537
4917
|
return;
|
|
4538
4918
|
}
|
|
4539
4919
|
if (style === 'double' && thickness >= 3) {
|
|
4540
4920
|
const band = doubleBand(thickness);
|
|
4541
4921
|
if (horizontal) {
|
|
4542
|
-
|
|
4543
|
-
|
|
4922
|
+
fill(x, y, length, band);
|
|
4923
|
+
fill(x, y + thickness - band, length, band);
|
|
4544
4924
|
}
|
|
4545
4925
|
else {
|
|
4546
|
-
|
|
4547
|
-
|
|
4926
|
+
fill(x, y, band, length);
|
|
4927
|
+
fill(x + thickness - band, y, band, length);
|
|
4548
4928
|
}
|
|
4549
4929
|
return;
|
|
4550
4930
|
}
|
|
4551
|
-
|
|
4931
|
+
fill(x, y, w, h);
|
|
4552
4932
|
}
|
|
4553
4933
|
/** A list item's bullet or number, in the margin. */
|
|
4554
4934
|
function paintMarker(ctx, marker, options,
|
|
@@ -4943,7 +5323,7 @@ function paintClippedText(ctx, block, lines, options) {
|
|
|
4943
5323
|
Math.max(natural.y + natural.height, natural.baseline + (natural.descent ?? 0)) +
|
|
4944
5324
|
2;
|
|
4945
5325
|
for (const run of natural.runs) {
|
|
4946
|
-
const clip = clipBoxOf(text.spans.boxAt?.(run.start) ?? null
|
|
5326
|
+
const clip = clipBoxOf(text.spans.boxAt?.(run.start) ?? null);
|
|
4947
5327
|
if (!clip)
|
|
4948
5328
|
continue;
|
|
4949
5329
|
let byLayout = parts.get(clip);
|
|
@@ -4967,8 +5347,8 @@ function paintClippedText(ctx, block, lines, options) {
|
|
|
4967
5347
|
const list = [...byLayout.values()];
|
|
4968
5348
|
const style = clip.style;
|
|
4969
5349
|
let area;
|
|
4970
|
-
if (clip
|
|
4971
|
-
area = paddingBox(
|
|
5350
|
+
if (clip.kind !== 'inline')
|
|
5351
|
+
area = paddingBox(clip, options);
|
|
4972
5352
|
else {
|
|
4973
5353
|
// an inline box: its runs, and its padding round them
|
|
4974
5354
|
let x0 = Infinity;
|