@react-x11/components 0.14.1 → 0.16.1
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 +27 -8
- 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 +29 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +172 -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 +1759 -238
- 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 +64 -19
- 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 +122 -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 +27 -8
- 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 +211 -27
- package/src/html/layout/css-grid.ts +12 -9
- package/src/html/layout/flex.ts +1975 -231
- package/src/html/layout/inline.ts +70 -16
- package/src/html/layout/table.ts +961 -332
- package/src/html/node.ts +155 -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/src/html/paint.ts
CHANGED
|
@@ -101,7 +101,7 @@ import {
|
|
|
101
101
|
} from './layout/inline.js';
|
|
102
102
|
import { tableGrid } from './layout/grid.js';
|
|
103
103
|
import type { Cell } from './layout/grid.js';
|
|
104
|
-
import { SvgDrawing, inlineDrawing } from './svg.js';
|
|
104
|
+
import { SvgDrawing, concreteSize, inlineDrawing } from './svg.js';
|
|
105
105
|
import type { IntrinsicSize } from './svg.js';
|
|
106
106
|
import type { CollapsedBorder } from './layout/collapse.js';
|
|
107
107
|
|
|
@@ -976,20 +976,15 @@ function originBox(
|
|
|
976
976
|
* it falls nearest, as browsers snap a box — boxes that meet share the
|
|
977
977
|
* column their edge is in, and a rule 1.33px wide is one pixel, not two —
|
|
978
978
|
* with the border box's corners less the widths between (5.3), where
|
|
979
|
-
* `rounded` asks for them
|
|
979
|
+
* `rounded` asks for them — cut to what the paint reaches (`clampArea`).
|
|
980
|
+
* Null where it has no area there.
|
|
980
981
|
*/
|
|
981
982
|
function clipArea(
|
|
982
983
|
box: Frame,
|
|
983
984
|
options: PaintOptions,
|
|
984
985
|
style: ComputedStyle,
|
|
985
986
|
rounded: boolean,
|
|
986
|
-
): {
|
|
987
|
-
x: number;
|
|
988
|
-
y: number;
|
|
989
|
-
w: number;
|
|
990
|
-
h: number;
|
|
991
|
-
corners: Corners | null;
|
|
992
|
-
} | null {
|
|
987
|
+
): Area | null {
|
|
993
988
|
const left = box.x + options.originX;
|
|
994
989
|
const top = frameY(box) + options.originY;
|
|
995
990
|
const right = left + box.width;
|
|
@@ -1018,7 +1013,7 @@ function clipArea(
|
|
|
1018
1013
|
-l,
|
|
1019
1014
|
);
|
|
1020
1015
|
}
|
|
1021
|
-
return
|
|
1016
|
+
return clampArea(options, x, y, w, h, corners);
|
|
1022
1017
|
}
|
|
1023
1018
|
|
|
1024
1019
|
/** What the background and border painters read of a box — which an
|
|
@@ -1744,9 +1739,7 @@ function frameImages(
|
|
|
1744
1739
|
options: PaintOptions,
|
|
1745
1740
|
): (layer: ComputedStyle) => void {
|
|
1746
1741
|
return (layer) => {
|
|
1747
|
-
const
|
|
1748
|
-
const area =
|
|
1749
|
-
painted && clampRect(options, painted.x, painted.y, painted.w, painted.h);
|
|
1742
|
+
const area = clipArea(frame, options, layer, true);
|
|
1750
1743
|
if (area) {
|
|
1751
1744
|
paintBackgroundImage(
|
|
1752
1745
|
ctx,
|
|
@@ -1754,7 +1747,7 @@ function frameImages(
|
|
|
1754
1747
|
area,
|
|
1755
1748
|
originBox(frame, options, layer),
|
|
1756
1749
|
options,
|
|
1757
|
-
|
|
1750
|
+
area.corners,
|
|
1758
1751
|
);
|
|
1759
1752
|
}
|
|
1760
1753
|
};
|
|
@@ -1768,6 +1761,9 @@ function shadowReach(blur: number): number {
|
|
|
1768
1761
|
|
|
1769
1762
|
const SQUARE: Corners = { x: [0, 0, 0, 0], y: [0, 0, 0, 0] };
|
|
1770
1763
|
|
|
1764
|
+
/** A rectangle with no area, where a ring has no inside. */
|
|
1765
|
+
const NOWHERE: Rect = { x: 0, y: 0, width: 0, height: 0 };
|
|
1766
|
+
|
|
1771
1767
|
/** Corners shrunk by `by` — an inner shadow's spread — or grown where it
|
|
1772
1768
|
* is negative; a square corner stays square. */
|
|
1773
1769
|
function grownCorners(c: Corners, by: number): Corners {
|
|
@@ -1845,15 +1841,6 @@ function paintShadows(
|
|
|
1845
1841
|
) {
|
|
1846
1842
|
continue;
|
|
1847
1843
|
}
|
|
1848
|
-
// Cut, as a background is (`clampRect`), to what the paint reaches
|
|
1849
|
-
// and as far again as the blur does, so the cut edges cast nothing
|
|
1850
|
-
// on it. A shadow down a box thousands of pixels tall went to the
|
|
1851
|
-
// server whole, and its outline, sent in 16.16 fixed point, threw.
|
|
1852
|
-
const within = reach + CLAMP_PAD;
|
|
1853
|
-
const cut = clampAround(options, shape, within);
|
|
1854
|
-
if (!cut) continue;
|
|
1855
|
-
Object.assign(shape, cut);
|
|
1856
|
-
const own = clampAround(options, rect, within) ?? rect;
|
|
1857
1844
|
const around =
|
|
1858
1845
|
spreadCorners(
|
|
1859
1846
|
corners,
|
|
@@ -1864,11 +1851,31 @@ function paintShadows(
|
|
|
1864
1851
|
s.spread,
|
|
1865
1852
|
s.spread,
|
|
1866
1853
|
) ?? SQUARE;
|
|
1854
|
+
// Cut, as a background is (`clampArea`), to what the paint reaches
|
|
1855
|
+
// and as far again as the blur does, so the cut edges cast nothing
|
|
1856
|
+
// on it, and the box with it, which a ring is the band outside and
|
|
1857
|
+
// the shadow is clipped out of. A shadow down a box thousands of
|
|
1858
|
+
// pixels tall went to the server whole, and its outline, sent in
|
|
1859
|
+
// 16.16 fixed point, threw.
|
|
1860
|
+
const [cut, own] = clampPair(
|
|
1861
|
+
options,
|
|
1862
|
+
reach + CLAMP_PAD,
|
|
1863
|
+
{ rect: shape, corners: around },
|
|
1864
|
+
{ rect, corners },
|
|
1865
|
+
);
|
|
1866
|
+
if (!cut) continue;
|
|
1867
|
+
const { x, y, width, height } = cut.rect;
|
|
1867
1868
|
const color = inkColor(s.color, style.color);
|
|
1868
1869
|
if (!(s.blur > 0) && !s.x && !s.y && s.spread >= 0 && !covered) {
|
|
1869
1870
|
// a ring about the box: the band between it and the spread
|
|
1870
1871
|
ctx.fillStyle = color;
|
|
1871
|
-
fillRing(
|
|
1872
|
+
fillRing(
|
|
1873
|
+
ctx,
|
|
1874
|
+
cut.rect,
|
|
1875
|
+
cut.corners,
|
|
1876
|
+
own ? own.rect : NOWHERE,
|
|
1877
|
+
own ? own.corners : SQUARE,
|
|
1878
|
+
);
|
|
1872
1879
|
continue;
|
|
1873
1880
|
}
|
|
1874
1881
|
if (!(s.blur > 0) && covered) {
|
|
@@ -1878,14 +1885,7 @@ function paintShadows(
|
|
|
1878
1885
|
s,
|
|
1879
1886
|
color,
|
|
1880
1887
|
(dx) => {
|
|
1881
|
-
roundedRect(
|
|
1882
|
-
ctx,
|
|
1883
|
-
shape.x - dx,
|
|
1884
|
-
shape.y,
|
|
1885
|
-
shape.width,
|
|
1886
|
-
shape.height,
|
|
1887
|
-
around,
|
|
1888
|
-
);
|
|
1888
|
+
roundedRect(ctx, x - dx, y, width, height, cut.corners);
|
|
1889
1889
|
},
|
|
1890
1890
|
0,
|
|
1891
1891
|
);
|
|
@@ -1897,21 +1897,24 @@ function paintShadows(
|
|
|
1897
1897
|
ctx.save();
|
|
1898
1898
|
ctx.beginPath();
|
|
1899
1899
|
ctx.rect!(
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
);
|
|
1905
|
-
roundedRect(
|
|
1906
|
-
ctx,
|
|
1907
|
-
own.x,
|
|
1908
|
-
own.y,
|
|
1909
|
-
own.width,
|
|
1910
|
-
own.height,
|
|
1911
|
-
corners,
|
|
1912
|
-
true,
|
|
1913
|
-
true,
|
|
1900
|
+
x - reach - 1,
|
|
1901
|
+
y - reach - 1,
|
|
1902
|
+
width + 2 * reach + 2,
|
|
1903
|
+
height + 2 * reach + 2,
|
|
1914
1904
|
);
|
|
1905
|
+
if (own) {
|
|
1906
|
+
const { rect: r } = own;
|
|
1907
|
+
roundedRect(
|
|
1908
|
+
ctx,
|
|
1909
|
+
r.x,
|
|
1910
|
+
r.y,
|
|
1911
|
+
r.width,
|
|
1912
|
+
r.height,
|
|
1913
|
+
own.corners,
|
|
1914
|
+
true,
|
|
1915
|
+
true,
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1915
1918
|
ctx.clip!();
|
|
1916
1919
|
}
|
|
1917
1920
|
fillShadow(
|
|
@@ -1919,16 +1922,9 @@ function paintShadows(
|
|
|
1919
1922
|
s,
|
|
1920
1923
|
color,
|
|
1921
1924
|
(dx) => {
|
|
1922
|
-
shadowShape(
|
|
1923
|
-
ctx,
|
|
1924
|
-
shape.x - dx,
|
|
1925
|
-
shape.y,
|
|
1926
|
-
shape.width,
|
|
1927
|
-
shape.height,
|
|
1928
|
-
around,
|
|
1929
|
-
);
|
|
1925
|
+
shadowShape(ctx, x - dx, y, width, height, cut.corners);
|
|
1930
1926
|
},
|
|
1931
|
-
|
|
1927
|
+
x + width + reach,
|
|
1932
1928
|
);
|
|
1933
1929
|
if (clipped) ctx.restore();
|
|
1934
1930
|
}
|
|
@@ -1954,9 +1950,6 @@ function paintShadows(
|
|
|
1954
1950
|
);
|
|
1955
1951
|
}
|
|
1956
1952
|
}
|
|
1957
|
-
const padCut = clampAround(options, pad, furthest + CLAMP_PAD);
|
|
1958
|
-
if (!padCut) return;
|
|
1959
|
-
Object.assign(pad, padCut);
|
|
1960
1953
|
const inner = insetCorners(
|
|
1961
1954
|
corners,
|
|
1962
1955
|
box.borderTop,
|
|
@@ -1968,32 +1961,50 @@ function paintShadows(
|
|
|
1968
1961
|
const s = shadows[i];
|
|
1969
1962
|
if (!s.inset || (s.blur > 0 && !canShadow)) continue;
|
|
1970
1963
|
const color = inkColor(s.color, style.color);
|
|
1971
|
-
//
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1964
|
+
// The hole the shadow is cast around, moved and shrunk by the spread,
|
|
1965
|
+
// cut with the padding box: made from the cut one, it was a spread in
|
|
1966
|
+
// from the cut rather than from the box's edge, and a spread wider
|
|
1967
|
+
// than the margin left none.
|
|
1968
|
+
const [area, hole] = clampPair(
|
|
1969
|
+
options,
|
|
1970
|
+
furthest + CLAMP_PAD,
|
|
1971
|
+
{ rect: pad, corners: inner },
|
|
1972
|
+
{
|
|
1973
|
+
rect: {
|
|
1974
|
+
x: pad.x + s.x + s.spread,
|
|
1975
|
+
y: pad.y + s.y + s.spread,
|
|
1976
|
+
width: Math.max(0, pad.width - 2 * s.spread),
|
|
1977
|
+
height: Math.max(0, pad.height - 2 * s.spread),
|
|
1978
|
+
},
|
|
1979
|
+
corners: grownCorners(inner, -s.spread),
|
|
1980
|
+
},
|
|
1981
|
+
);
|
|
1982
|
+
if (!area) continue;
|
|
1979
1983
|
if (!(s.blur > 0) && !s.x && !s.y && s.spread >= 0) {
|
|
1980
1984
|
// Tailwind's `ring-inset`: a band inside the padding edge
|
|
1981
1985
|
ctx.fillStyle = color;
|
|
1982
|
-
fillRing(
|
|
1986
|
+
fillRing(
|
|
1987
|
+
ctx,
|
|
1988
|
+
area.rect,
|
|
1989
|
+
area.corners,
|
|
1990
|
+
hole ? hole.rect : NOWHERE,
|
|
1991
|
+
hole ? hole.corners : SQUARE,
|
|
1992
|
+
);
|
|
1983
1993
|
continue;
|
|
1984
1994
|
}
|
|
1985
1995
|
if (!ctx.clip || !ctx.save || !ctx.rect) continue;
|
|
1996
|
+
const { x, y, width, height } = area.rect;
|
|
1986
1997
|
ctx.save();
|
|
1987
1998
|
ctx.beginPath();
|
|
1988
|
-
roundedRect(ctx,
|
|
1999
|
+
roundedRect(ctx, x, y, width, height, area.corners);
|
|
1989
2000
|
ctx.clip();
|
|
1990
2001
|
// a frame around the hole, as wide as the blur and the offset reach
|
|
1991
2002
|
const reach = shadowReach(s.blur) + Math.abs(s.x) + Math.abs(s.y) + 1;
|
|
1992
2003
|
const frame = {
|
|
1993
|
-
x:
|
|
1994
|
-
y:
|
|
1995
|
-
width:
|
|
1996
|
-
height:
|
|
2004
|
+
x: x - reach,
|
|
2005
|
+
y: y - reach,
|
|
2006
|
+
width: width + 2 * reach,
|
|
2007
|
+
height: height + 2 * reach,
|
|
1997
2008
|
};
|
|
1998
2009
|
fillShadow(
|
|
1999
2010
|
ctx,
|
|
@@ -2001,15 +2012,9 @@ function paintShadows(
|
|
|
2001
2012
|
color,
|
|
2002
2013
|
(dx) => {
|
|
2003
2014
|
ctx.rect!(frame.x - dx, frame.y, frame.width, frame.height);
|
|
2004
|
-
if (hole
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
hole.x - dx,
|
|
2008
|
-
hole.y,
|
|
2009
|
-
hole.width,
|
|
2010
|
-
hole.height,
|
|
2011
|
-
within,
|
|
2012
|
-
);
|
|
2015
|
+
if (hole) {
|
|
2016
|
+
const { rect: r } = hole;
|
|
2017
|
+
shadowShape(ctx, r.x - dx, r.y, r.width, r.height, hole.corners);
|
|
2013
2018
|
}
|
|
2014
2019
|
},
|
|
2015
2020
|
frame.x + frame.width + shadowReach(s.blur),
|
|
@@ -3417,6 +3422,92 @@ function clampRect(
|
|
|
3417
3422
|
return { x: x1, y: y1, w: x2 - x1, h: y2 - y1 };
|
|
3418
3423
|
}
|
|
3419
3424
|
|
|
3425
|
+
/** A rectangle with its corners, square where they are null. */
|
|
3426
|
+
interface Area {
|
|
3427
|
+
x: number;
|
|
3428
|
+
y: number;
|
|
3429
|
+
w: number;
|
|
3430
|
+
h: number;
|
|
3431
|
+
corners: Corners | null;
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
/**
|
|
3435
|
+
* `clampRect` for a rectangle that may be rounded, with the corners it is
|
|
3436
|
+
* left with. Where no radius is larger than `CLAMP_PAD` they are its own,
|
|
3437
|
+
* and it is cut as a square one is: a corner the cut leaves on a cut edge
|
|
3438
|
+
* curves no further in from it than the margin, which keeps it out of what
|
|
3439
|
+
* is painted. A larger radius, a circle's or a pill's, would bend into it
|
|
3440
|
+
* — a circle 400 across repainted a strip at a time was another shape in
|
|
3441
|
+
* every strip across its curve — so there the window is moved out past
|
|
3442
|
+
* any curve it would cross, and a corner on a side it cut is square
|
|
3443
|
+
* (`cutRounded`, as a rounded border's ring is cut).
|
|
3444
|
+
*/
|
|
3445
|
+
function clampArea(
|
|
3446
|
+
options: PaintOptions,
|
|
3447
|
+
x: number,
|
|
3448
|
+
y: number,
|
|
3449
|
+
w: number,
|
|
3450
|
+
h: number,
|
|
3451
|
+
corners: Corners | null,
|
|
3452
|
+
): Area | null {
|
|
3453
|
+
if (corners && curvesPast(corners, CLAMP_PAD)) {
|
|
3454
|
+
const shape = { rect: { x, y, width: w, height: h }, corners };
|
|
3455
|
+
const cut = cutRounded(roundedWindow(options, [shape]), shape);
|
|
3456
|
+
if (!cut) return null;
|
|
3457
|
+
const { rect } = cut;
|
|
3458
|
+
return {
|
|
3459
|
+
x: rect.x,
|
|
3460
|
+
y: rect.y,
|
|
3461
|
+
w: rect.width,
|
|
3462
|
+
h: rect.height,
|
|
3463
|
+
corners: cut.corners,
|
|
3464
|
+
};
|
|
3465
|
+
}
|
|
3466
|
+
const rect = clampRect(options, x, y, w, h);
|
|
3467
|
+
return rect && { x: rect.x, y: rect.y, w: rect.w, h: rect.h, corners };
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
/**
|
|
3471
|
+
* Two rounded rectangles drawn against each other — a shadow's shape and
|
|
3472
|
+
* the box it is clipped out of, a hole and what it is cut in — cut to one
|
|
3473
|
+
* window, `pad` round what the paint reaches, as `clampArea` cuts one:
|
|
3474
|
+
* where neither has a corner that curves further than `CLAMP_PAD`, as
|
|
3475
|
+
* square ones are, keeping their corners, and otherwise to a window moved
|
|
3476
|
+
* out past every curve of either it would cross, each square on the sides
|
|
3477
|
+
* it cut. `CLAMP_PAD` and not `pad`, because what a shadow's margin has
|
|
3478
|
+
* past it is what the blur casts back across the cut, and a corner bent
|
|
3479
|
+
* into the margin is cast that far further in. Null for one with nothing
|
|
3480
|
+
* in the window.
|
|
3481
|
+
*/
|
|
3482
|
+
function clampPair(
|
|
3483
|
+
options: PaintOptions,
|
|
3484
|
+
pad: number,
|
|
3485
|
+
a: Rounded,
|
|
3486
|
+
b: Rounded,
|
|
3487
|
+
): [Rounded | null, Rounded | null] {
|
|
3488
|
+
if (!curvesPast(a.corners, CLAMP_PAD) && !curvesPast(b.corners, CLAMP_PAD)) {
|
|
3489
|
+
return [clampKeeping(options, a, pad), clampKeeping(options, b, pad)];
|
|
3490
|
+
}
|
|
3491
|
+
const cut = roundedWindow(options, [a, b], pad);
|
|
3492
|
+
return [cutRounded(cut, a), cutRounded(cut, b)];
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
/** `clampAround` for a rounded rectangle, keeping its corners. */
|
|
3496
|
+
function clampKeeping(
|
|
3497
|
+
options: PaintOptions,
|
|
3498
|
+
{ rect, corners }: Rounded,
|
|
3499
|
+
pad: number,
|
|
3500
|
+
): Rounded | null {
|
|
3501
|
+
const cut = clampAround(options, rect, pad);
|
|
3502
|
+
return cut && { rect: cut, corners };
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
/** Whether a corner curves further than `by` along either of its sides. */
|
|
3506
|
+
function curvesPast({ x, y }: Corners, by: number): boolean {
|
|
3507
|
+
for (let i = 0; i < 4; i += 1) if (x[i] > by || y[i] > by) return true;
|
|
3508
|
+
return false;
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3420
3511
|
function paintBackground(
|
|
3421
3512
|
ctx: PaintContext,
|
|
3422
3513
|
box: Frame,
|
|
@@ -3427,20 +3518,16 @@ function paintBackground(
|
|
|
3427
3518
|
const gradient = style.backgroundGradient;
|
|
3428
3519
|
const solid = !isTransparent(color);
|
|
3429
3520
|
if (!solid && !gradient) return;
|
|
3430
|
-
const
|
|
3521
|
+
const rect = clipArea(
|
|
3431
3522
|
box,
|
|
3432
3523
|
options,
|
|
3433
3524
|
style,
|
|
3434
3525
|
!!(ctx.roundRect && ctx.fill && ctx.beginPath),
|
|
3435
3526
|
);
|
|
3436
|
-
const rect = area && clampRect(options, area.x, area.y, area.w, area.h);
|
|
3437
3527
|
if (!rect) return;
|
|
3438
|
-
const rounded =
|
|
3528
|
+
const rounded = rect.corners;
|
|
3439
3529
|
const fill = (): void => {
|
|
3440
3530
|
if (rounded) {
|
|
3441
|
-
// The clamp can only have cut edges further than CLAMP_PAD outside
|
|
3442
|
-
// the damage, and a sane radius is smaller than that — so a corner
|
|
3443
|
-
// that survives the cut is whole, and a cut edge is offscreen.
|
|
3444
3531
|
ctx.beginPath!();
|
|
3445
3532
|
roundedRect(ctx, rect.x, rect.y, rect.w, rect.h, rounded);
|
|
3446
3533
|
ctx.fill!();
|
|
@@ -4355,7 +4442,7 @@ function roundedTile(
|
|
|
4355
4442
|
|
|
4356
4443
|
/**
|
|
4357
4444
|
* A background tile's size (CSS Backgrounds 3, 3.9, over CSS Images' sizing
|
|
4358
|
-
* of an object): `auto` is the image's own, as `
|
|
4445
|
+
* of an object): `auto` is the image's own, as `concreteSize` has it; `cover`
|
|
4359
4446
|
* and `contain` scale it to fill the positioning area or to fit inside it,
|
|
4360
4447
|
* keeping its ratio, and one with no ratio is the size of the area; and a
|
|
4361
4448
|
* width or a height alone takes the other from the ratio, or else from the
|
|
@@ -4368,7 +4455,8 @@ function sizedTile(
|
|
|
4368
4455
|
area: Rect,
|
|
4369
4456
|
scale: number,
|
|
4370
4457
|
): [number, number] {
|
|
4371
|
-
if (size === 'auto')
|
|
4458
|
+
if (size === 'auto')
|
|
4459
|
+
return concreteSize(image, area.width, area.height, scale);
|
|
4372
4460
|
const { ratio } = image;
|
|
4373
4461
|
if (size === 'cover' || size === 'contain') {
|
|
4374
4462
|
if (!(ratio > 0 && area.height > 0)) return [area.width, area.height];
|
|
@@ -4389,34 +4477,7 @@ function sizedTile(
|
|
|
4389
4477
|
if (ratio > 0) return [h * ratio, h];
|
|
4390
4478
|
return [image.width === null ? area.width : image.width * scale, h];
|
|
4391
4479
|
}
|
|
4392
|
-
return
|
|
4393
|
-
}
|
|
4394
|
-
|
|
4395
|
-
/**
|
|
4396
|
-
* A background image's size, in device pixels, where nothing sets it — CSS
|
|
4397
|
-
* 2.1 has no `background-size` — which is CSS Images' default sizing: an
|
|
4398
|
-
* image's own size where it has one; the dimension it lacks from its ratio,
|
|
4399
|
-
* or else from the positioning area; and one with a ratio alone as large as
|
|
4400
|
-
* fits in the area. An SVG may be any of these, and a raster image is the
|
|
4401
|
-
* first.
|
|
4402
|
-
*/
|
|
4403
|
-
function tileSize(
|
|
4404
|
-
size: IntrinsicSize,
|
|
4405
|
-
area: Rect,
|
|
4406
|
-
scale: number,
|
|
4407
|
-
): [number, number] {
|
|
4408
|
-
const { ratio } = size;
|
|
4409
|
-
const width = size.width === null ? null : size.width * scale;
|
|
4410
|
-
const height = size.height === null ? null : size.height * scale;
|
|
4411
|
-
if (width !== null && height !== null) return [width, height];
|
|
4412
|
-
if (width !== null) return [width, ratio > 0 ? width / ratio : area.height];
|
|
4413
|
-
if (height !== null) return [ratio > 0 ? height * ratio : area.width, height];
|
|
4414
|
-
if (ratio > 0) {
|
|
4415
|
-
return area.width / area.height > ratio
|
|
4416
|
-
? [area.height * ratio, area.height]
|
|
4417
|
-
: [area.width, area.width / ratio];
|
|
4418
|
-
}
|
|
4419
|
-
return [area.width, area.height];
|
|
4480
|
+
return concreteSize(image, area.width, area.height, scale);
|
|
4420
4481
|
}
|
|
4421
4482
|
|
|
4422
4483
|
/**
|
|
@@ -4489,7 +4550,7 @@ function paintOutline(
|
|
|
4489
4550
|
*
|
|
4490
4551
|
* Where two sides that meet differ in colour — one of them transparent
|
|
4491
4552
|
* included — the corner is cut on the diagonal from its outer point to its
|
|
4492
|
-
* inner one and each side takes its half (CSS Backgrounds 3, 4.
|
|
4553
|
+
* inner one and each side takes its half (CSS Backgrounds 3, 4.4), so a side
|
|
4493
4554
|
* is a trapezoid, and a triangle where the box inside the borders has no
|
|
4494
4555
|
* width: the CSS triangle a dropdown's caret and a tooltip's arrow are
|
|
4495
4556
|
* drawn with, which came out as the rectangle around it. Only a solid side
|
|
@@ -4606,7 +4667,7 @@ function paintBorders(
|
|
|
4606
4667
|
|
|
4607
4668
|
/**
|
|
4608
4669
|
* Whether the corner two sides meet at is cut on its diagonal, each side
|
|
4609
|
-
* taking its half (CSS Backgrounds 3, 4.
|
|
4670
|
+
* taking its half (CSS Backgrounds 3, 4.4): where both have a width, they
|
|
4610
4671
|
* are painted in different colours or only one is painted at all, and each
|
|
4611
4672
|
* is solid or unpainted. Sides of one colour make the same corner either
|
|
4612
4673
|
* way, and so does a corner of a single pixel, which is left to the side
|
|
@@ -4628,12 +4689,14 @@ function mitred(
|
|
|
4628
4689
|
}
|
|
4629
4690
|
|
|
4630
4691
|
/**
|
|
4631
|
-
* Fill a
|
|
4632
|
-
*
|
|
4633
|
-
*
|
|
4634
|
-
*
|
|
4635
|
-
*
|
|
4636
|
-
*
|
|
4692
|
+
* Fill a polygon, its corners as `x, y` pairs, cut to `area`: the part of
|
|
4693
|
+
* it near what is painted, which is what keeps a side thousands of pixels
|
|
4694
|
+
* long inside X's coordinates. Cut edge by edge rather than by moving its
|
|
4695
|
+
* corners in, which would turn a diagonal that crosses the painted area: a
|
|
4696
|
+
* slanted divider, `border-left: 100vw solid transparent`, has a corner far
|
|
4697
|
+
* outside it. A polygon that is not convex, a rounded ring's share, may
|
|
4698
|
+
* come out of the cut with an edge run there and back along the area's
|
|
4699
|
+
* border, which fills nothing, and that border is outside the paint.
|
|
4637
4700
|
*/
|
|
4638
4701
|
function fillPolygon(
|
|
4639
4702
|
ctx: PaintContext,
|
|
@@ -4710,9 +4773,7 @@ function paintBorderImage(
|
|
|
4710
4773
|
// the image's size, CSS Images' default sizing in the area: a drawing
|
|
4711
4774
|
// with no size of its own is as large as it fits there, and a gradient,
|
|
4712
4775
|
// which has none, is the area's
|
|
4713
|
-
const [cw, ch] = loaded
|
|
4714
|
-
? tileSize(loaded, { x: 0, y: 0, width: aw, height: ah }, scale)
|
|
4715
|
-
: [aw, ah];
|
|
4776
|
+
const [cw, ch] = loaded ? concreteSize(loaded, aw, ah, scale) : [aw, ah];
|
|
4716
4777
|
const iw = cw / scale;
|
|
4717
4778
|
const ih = ch / scale;
|
|
4718
4779
|
if (!(iw > 0 && ih > 0)) return false;
|
|
@@ -4920,13 +4981,14 @@ function edgeRun(
|
|
|
4920
4981
|
|
|
4921
4982
|
/**
|
|
4922
4983
|
* A rounded box's border as the ring between its border edge and its
|
|
4923
|
-
* padding edge, each rounded, where every side that has a border has
|
|
4924
|
-
*
|
|
4925
|
-
*
|
|
4926
|
-
*
|
|
4927
|
-
*
|
|
4928
|
-
*
|
|
4929
|
-
*
|
|
4984
|
+
* padding edge, each rounded, where every side that has a border has a
|
|
4985
|
+
* solid rule: a card's or a button's. Drawn straight, its corners were
|
|
4986
|
+
* square over the background's rounded ones, and an accent border down one
|
|
4987
|
+
* side did not follow the corner. The inner radius is the outer less the
|
|
4988
|
+
* wider border at that corner (CSS Backgrounds 3, 5.2). Sides of one colour
|
|
4989
|
+
* are one fill; sides of more are each given their share of the ring
|
|
4990
|
+
* (`ringSides`). False where the border is not such a one, and it is drawn
|
|
4991
|
+
* a side at a time.
|
|
4930
4992
|
*/
|
|
4931
4993
|
function roundedRing(
|
|
4932
4994
|
ctx: PaintContext,
|
|
@@ -4938,7 +5000,7 @@ function roundedRing(
|
|
|
4938
5000
|
options: PaintOptions,
|
|
4939
5001
|
): boolean {
|
|
4940
5002
|
const s = box.style;
|
|
4941
|
-
if (!ctx.
|
|
5003
|
+
if (!ctx.fill || !ctx.beginPath) return false;
|
|
4942
5004
|
const corners = cornersOf(s, w, h);
|
|
4943
5005
|
if (!corners) return false;
|
|
4944
5006
|
const sides: [number, string, string][] = [
|
|
@@ -4948,13 +5010,34 @@ function roundedRing(
|
|
|
4948
5010
|
[box.borderLeft, s.borderLeftColor, s.borderLeftStyle],
|
|
4949
5011
|
];
|
|
4950
5012
|
let color: string | null = null;
|
|
5013
|
+
let mixed = false;
|
|
4951
5014
|
for (const [width, ink, style] of sides) {
|
|
4952
5015
|
if (!width) continue;
|
|
4953
|
-
if (style !== 'solid'
|
|
5016
|
+
if (style !== 'solid') return false;
|
|
5017
|
+
if (color !== null && ink !== color) mixed = true;
|
|
4954
5018
|
color = ink;
|
|
4955
5019
|
}
|
|
5020
|
+
if (mixed) return ringSides(ctx, box, x, y, w, h, corners, options);
|
|
5021
|
+
if (!ctx.roundRect) return false;
|
|
4956
5022
|
if (color === null) return true;
|
|
4957
5023
|
if (isTransparent(color)) return true;
|
|
5024
|
+
wholeRing(ctx, box, inkColor(color, s.color), x, y, w, h, corners, options);
|
|
5025
|
+
return true;
|
|
5026
|
+
}
|
|
5027
|
+
|
|
5028
|
+
/** The ring of a rounded border in one colour, cut to what the paint
|
|
5029
|
+
* reaches (`cutRing`). */
|
|
5030
|
+
function wholeRing(
|
|
5031
|
+
ctx: PaintContext,
|
|
5032
|
+
box: Frame,
|
|
5033
|
+
color: string,
|
|
5034
|
+
x: number,
|
|
5035
|
+
y: number,
|
|
5036
|
+
w: number,
|
|
5037
|
+
h: number,
|
|
5038
|
+
corners: Corners,
|
|
5039
|
+
options: PaintOptions,
|
|
5040
|
+
): void {
|
|
4958
5041
|
const ring = cutRing(
|
|
4959
5042
|
options,
|
|
4960
5043
|
x,
|
|
@@ -4967,16 +5050,350 @@ function roundedRing(
|
|
|
4967
5050
|
box.borderBottom,
|
|
4968
5051
|
box.borderLeft,
|
|
4969
5052
|
);
|
|
4970
|
-
if (!ring) return
|
|
5053
|
+
if (!ring) return;
|
|
4971
5054
|
const { outer, inner } = ring;
|
|
4972
|
-
ctx.fillStyle =
|
|
5055
|
+
ctx.fillStyle = color;
|
|
4973
5056
|
fillRing(
|
|
4974
5057
|
ctx,
|
|
4975
5058
|
outer.rect,
|
|
4976
5059
|
outer.corners,
|
|
4977
|
-
inner ? inner.rect :
|
|
5060
|
+
inner ? inner.rect : NOWHERE,
|
|
4978
5061
|
inner ? inner.corners : outer.corners,
|
|
4979
5062
|
);
|
|
5063
|
+
}
|
|
5064
|
+
|
|
5065
|
+
/** How far a chord of a flattened corner may stray from its curve, in
|
|
5066
|
+
* device pixels: a sixteenth, well inside what antialiasing shows. */
|
|
5067
|
+
const CURVE_TOLERANCE = 1 / 16;
|
|
5068
|
+
|
|
5069
|
+
/** How far past a cut a share reaches under the one drawn over it, in
|
|
5070
|
+
* device pixels: past the antialiased edge of either. */
|
|
5071
|
+
const SEAM = 1.5;
|
|
5072
|
+
|
|
5073
|
+
/** Which way a border's sides run at each corner, clockwise from the top
|
|
5074
|
+
* left: the side that ends at corner i arrives along `ALONG[i]`, and the
|
|
5075
|
+
* side that begins there leaves along `ALONG[i + 1]`. */
|
|
5076
|
+
const ALONG: readonly (readonly [number, number])[] = [
|
|
5077
|
+
[0, -1],
|
|
5078
|
+
[1, 0],
|
|
5079
|
+
[0, 1],
|
|
5080
|
+
[-1, 0],
|
|
5081
|
+
];
|
|
5082
|
+
|
|
5083
|
+
/**
|
|
5084
|
+
* A rounded border whose solid sides are not all one colour, each side's
|
|
5085
|
+
* share of the ring filled in its own. Where two sides of different colours
|
|
5086
|
+
* meet, the corner is cut on the line from the border box's corner through
|
|
5087
|
+
* the padding box's, as Blink cuts it: CSS Backgrounds 3, 4.4 puts the
|
|
5088
|
+
* transition on the curve at a point that follows the ratio of the two
|
|
5089
|
+
* widths and leaves the rest to the UA. A corner between a side and one
|
|
5090
|
+
* with no width is all the side's (4.4 again), and sides of one colour that
|
|
5091
|
+
* meet are one fill, so nothing is cut where the colour does not change.
|
|
5092
|
+
* Drawn a side at a time, the spinner the web draws — `border-radius: 50%`
|
|
5093
|
+
* with one side of another colour — was a square frame.
|
|
5094
|
+
*
|
|
5095
|
+
* A share is a polygon: the curves flattened to chords no further than
|
|
5096
|
+
* `CURVE_TOLERANCE` from them, so that it is cut to what the paint reaches
|
|
5097
|
+
* as the square corners are (`fillPolygon`) and a large ring repainted a
|
|
5098
|
+
* strip at a time is the same ring. False where the context draws no
|
|
5099
|
+
* lines, and the sides are drawn straight.
|
|
5100
|
+
*/
|
|
5101
|
+
function ringSides(
|
|
5102
|
+
ctx: PaintContext,
|
|
5103
|
+
box: Frame,
|
|
5104
|
+
x: number,
|
|
5105
|
+
y: number,
|
|
5106
|
+
w: number,
|
|
5107
|
+
h: number,
|
|
5108
|
+
corners: Corners,
|
|
5109
|
+
options: PaintOptions,
|
|
5110
|
+
): boolean {
|
|
5111
|
+
if (!ctx.moveTo || !ctx.lineTo) return false;
|
|
5112
|
+
const area = clampRect(options, x, y, w, h);
|
|
5113
|
+
if (!area) return true;
|
|
5114
|
+
const s = box.style;
|
|
5115
|
+
const widths = [
|
|
5116
|
+
box.borderTop,
|
|
5117
|
+
box.borderRight,
|
|
5118
|
+
box.borderBottom,
|
|
5119
|
+
box.borderLeft,
|
|
5120
|
+
];
|
|
5121
|
+
const colors = [
|
|
5122
|
+
s.borderTopColor,
|
|
5123
|
+
s.borderRightColor,
|
|
5124
|
+
s.borderBottomColor,
|
|
5125
|
+
s.borderLeftColor,
|
|
5126
|
+
];
|
|
5127
|
+
// what each side is painted in, null where it paints nothing
|
|
5128
|
+
const inks = widths.map((width, i) =>
|
|
5129
|
+
width > 0 && !isTransparent(colors[i])
|
|
5130
|
+
? inkColor(colors[i], s.color)
|
|
5131
|
+
: null,
|
|
5132
|
+
);
|
|
5133
|
+
// Corners from the top left, clockwise. Corner i is where side i - 1
|
|
5134
|
+
// ends and side i begins: the top left ends the left and begins the top.
|
|
5135
|
+
// Two sides of one colour are joined across their corner.
|
|
5136
|
+
const joined = [0, 1, 2, 3].map((i) => {
|
|
5137
|
+
const before = (i + 3) % 4;
|
|
5138
|
+
return widths[before] > 0 && widths[i] > 0 && inks[before] === inks[i];
|
|
5139
|
+
});
|
|
5140
|
+
if (!joined.includes(false)) {
|
|
5141
|
+
// one colour after all, `currentColor` and the colour it is
|
|
5142
|
+
if (!ctx.roundRect) return false;
|
|
5143
|
+
if (inks[0] !== null) {
|
|
5144
|
+
wholeRing(ctx, box, inks[0], x, y, w, h, corners, options);
|
|
5145
|
+
}
|
|
5146
|
+
return true;
|
|
5147
|
+
}
|
|
5148
|
+
const [t, r, b, l] = widths;
|
|
5149
|
+
const x1 = x + w;
|
|
5150
|
+
const y1 = y + h;
|
|
5151
|
+
// the padding box, inside the borders
|
|
5152
|
+
const ix0 = x + l;
|
|
5153
|
+
const iy0 = y + t;
|
|
5154
|
+
const ix1 = Math.max(ix0, x1 - r);
|
|
5155
|
+
const iy1 = Math.max(iy0, y1 - b);
|
|
5156
|
+
const inside = insetCorners(corners, t, r, b, l);
|
|
5157
|
+
const hollow = ix1 > ix0 && iy1 > iy0;
|
|
5158
|
+
// each corner's point on the border box and on the padding box, which
|
|
5159
|
+
// way it points, and the quarter ellipses that round the two there
|
|
5160
|
+
const sx = [-1, 1, 1, -1];
|
|
5161
|
+
const sy = [-1, -1, 1, 1];
|
|
5162
|
+
const ox = [x, x1, x1, x];
|
|
5163
|
+
const oy = [y, y, y1, y1];
|
|
5164
|
+
const px = [ix0, ix1, ix1, ix0];
|
|
5165
|
+
const py = [iy0, iy0, iy1, iy1];
|
|
5166
|
+
type Quarter = { cx: number; cy: number; rx: number; ry: number } | null;
|
|
5167
|
+
const quarter = (
|
|
5168
|
+
i: number,
|
|
5169
|
+
cornerX: number,
|
|
5170
|
+
cornerY: number,
|
|
5171
|
+
rx: number,
|
|
5172
|
+
ry: number,
|
|
5173
|
+
): Quarter =>
|
|
5174
|
+
rx > 0 && ry > 0
|
|
5175
|
+
? { cx: cornerX - sx[i] * rx, cy: cornerY - sy[i] * ry, rx, ry }
|
|
5176
|
+
: null;
|
|
5177
|
+
const outer: Quarter[] = [];
|
|
5178
|
+
const inner: Quarter[] = [];
|
|
5179
|
+
for (let i = 0; i < 4; i += 1) {
|
|
5180
|
+
outer.push(quarter(i, ox[i], oy[i], corners.x[i], corners.y[i]));
|
|
5181
|
+
inner.push(
|
|
5182
|
+
hollow ? quarter(i, px[i], py[i], inside.x[i], inside.y[i]) : null,
|
|
5183
|
+
);
|
|
5184
|
+
}
|
|
5185
|
+
// A corner's quarter turns clockwise from the angle it starts at: the top
|
|
5186
|
+
// left's from pointing left to pointing up.
|
|
5187
|
+
const QUARTER = Math.PI / 2;
|
|
5188
|
+
const start = (i: number): number => Math.PI + i * QUARTER;
|
|
5189
|
+
/** Where a line from (fromX, fromY) along (dx, dy) first meets a quarter
|
|
5190
|
+
* ellipse, as the angle it is at; the line starts outside it. */
|
|
5191
|
+
const crossing = (
|
|
5192
|
+
i: number,
|
|
5193
|
+
q: Quarter,
|
|
5194
|
+
fromX: number,
|
|
5195
|
+
fromY: number,
|
|
5196
|
+
dx: number,
|
|
5197
|
+
dy: number,
|
|
5198
|
+
): number => {
|
|
5199
|
+
const a0 = start(i);
|
|
5200
|
+
if (!q) return a0;
|
|
5201
|
+
const ux = (fromX - q.cx) / q.rx;
|
|
5202
|
+
const uy = (fromY - q.cy) / q.ry;
|
|
5203
|
+
const vx = dx / q.rx;
|
|
5204
|
+
const vy = dy / q.ry;
|
|
5205
|
+
const qa = vx * vx + vy * vy;
|
|
5206
|
+
const qb = ux * vx + uy * vy;
|
|
5207
|
+
const disc = qb * qb - qa * (ux * ux + uy * uy - 1);
|
|
5208
|
+
const k = qa > 0 && disc > 0 ? (-qb - Math.sqrt(disc)) / qa : 0;
|
|
5209
|
+
let a = Math.atan2(uy + k * vy, ux + k * vx);
|
|
5210
|
+
// into the quarter's own turn
|
|
5211
|
+
a += 2 * Math.PI * Math.round((a0 + QUARTER / 2 - a) / (2 * Math.PI));
|
|
5212
|
+
return Math.min(a0 + QUARTER, Math.max(a0, a));
|
|
5213
|
+
};
|
|
5214
|
+
// Where the shares meet at each corner, as angles on its two quarters: on
|
|
5215
|
+
// the cut, or at the end of the corner where one side has no width and
|
|
5216
|
+
// the other has all of it — the side that has none paints nothing.
|
|
5217
|
+
const cutOuter: number[] = [];
|
|
5218
|
+
const cutInner: number[] = [];
|
|
5219
|
+
for (let i = 0; i < 4; i += 1) {
|
|
5220
|
+
const before = widths[(i + 3) % 4] > 0;
|
|
5221
|
+
const after = widths[i] > 0;
|
|
5222
|
+
if (before && after) {
|
|
5223
|
+
const dx = px[i] - ox[i];
|
|
5224
|
+
const dy = py[i] - oy[i];
|
|
5225
|
+
cutOuter.push(crossing(i, outer[i], ox[i], oy[i], dx, dy));
|
|
5226
|
+
cutInner.push(crossing(i, inner[i], px[i], py[i], dx, dy));
|
|
5227
|
+
} else {
|
|
5228
|
+
const end = before ? start(i) + QUARTER : start(i);
|
|
5229
|
+
cutOuter.push(end);
|
|
5230
|
+
cutInner.push(end);
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
// The shares, a run of sides of one colour each, from the corner the
|
|
5234
|
+
// colour changes at to the next one it changes at. Those that show what
|
|
5235
|
+
// is under them are drawn first.
|
|
5236
|
+
const runs: { from: number; to: number; ink: string | null }[] = [];
|
|
5237
|
+
for (let i = 0; i < 4; i += 1) {
|
|
5238
|
+
if (joined[i]) continue;
|
|
5239
|
+
let j = (i + 1) % 4;
|
|
5240
|
+
while (joined[j]) j = (j + 1) % 4;
|
|
5241
|
+
runs.push({ from: i, to: j, ink: inks[i] });
|
|
5242
|
+
}
|
|
5243
|
+
const opaque = (ink: string | null): boolean =>
|
|
5244
|
+
ink !== null && alphaOf(ink) === 1;
|
|
5245
|
+
runs.sort((a, b) => Number(opaque(a.ink)) - Number(opaque(b.ink)));
|
|
5246
|
+
/** Whether a share reaches past its cut at a corner, under the one on
|
|
5247
|
+
* the other side of it: where that one is drawn after it and covers it. */
|
|
5248
|
+
const under = (k: number, corner: number): boolean => {
|
|
5249
|
+
const before = widths[(corner + 3) % 4] > 0;
|
|
5250
|
+
if (!(before && widths[corner] > 0)) return false;
|
|
5251
|
+
const other = runs.findIndex(
|
|
5252
|
+
(run) => run !== runs[k] && (run.from === corner || run.to === corner),
|
|
5253
|
+
);
|
|
5254
|
+
return other > k && opaque(runs[other].ink);
|
|
5255
|
+
};
|
|
5256
|
+
// The straight part of each side's edge, between the quarters at its
|
|
5257
|
+
// ends, on the border edge and the padding edge: as far as a share may
|
|
5258
|
+
// reach along it past a corner.
|
|
5259
|
+
const straight = (q: Quarter[], across: number, down: number): number[] => {
|
|
5260
|
+
const rx = (i: number) => q[i]?.rx ?? 0;
|
|
5261
|
+
const ry = (i: number) => q[i]?.ry ?? 0;
|
|
5262
|
+
return [
|
|
5263
|
+
across - rx(0) - rx(1),
|
|
5264
|
+
down - ry(1) - ry(2),
|
|
5265
|
+
across - rx(2) - rx(3),
|
|
5266
|
+
down - ry(3) - ry(0),
|
|
5267
|
+
].map((v) => Math.max(0, v));
|
|
5268
|
+
};
|
|
5269
|
+
const outerRoom = straight(outer, w, h);
|
|
5270
|
+
const innerRoom = straight(inner, ix1 - ix0, iy1 - iy0);
|
|
5271
|
+
/**
|
|
5272
|
+
* A share's end moved past the cut at a corner, onto the other share,
|
|
5273
|
+
* along an edge from the angle `a` it is at on a quarter: `forward` the
|
|
5274
|
+
* way the corner turns, onto the side that begins there, or back onto
|
|
5275
|
+
* the side that ends there. Far enough that the line it ends on is
|
|
5276
|
+
* `SEAM` from the cut, whatever angle the two make; as the angle it
|
|
5277
|
+
* reaches on the quarter and how far it goes on along the straight edge
|
|
5278
|
+
* past it, no further than `room`.
|
|
5279
|
+
*/
|
|
5280
|
+
const past = (
|
|
5281
|
+
q: Quarter,
|
|
5282
|
+
i: number,
|
|
5283
|
+
a: number,
|
|
5284
|
+
forward: boolean,
|
|
5285
|
+
room: number,
|
|
5286
|
+
): [number, number] => {
|
|
5287
|
+
const end = forward ? start(i) + QUARTER : start(i);
|
|
5288
|
+
const sign = forward ? 1 : -1;
|
|
5289
|
+
const [ex, ey] = forward ? ALONG[(i + 1) % 4] : ALONG[i];
|
|
5290
|
+
// the way it goes, and the sine of its angle with the cut
|
|
5291
|
+
let tx = forward ? ex : -ex;
|
|
5292
|
+
let ty = forward ? ey : -ey;
|
|
5293
|
+
if (q && a !== end) {
|
|
5294
|
+
const speed = Math.hypot(q.rx * Math.sin(a), q.ry * Math.cos(a));
|
|
5295
|
+
tx = (sign * -q.rx * Math.sin(a)) / speed;
|
|
5296
|
+
ty = (sign * q.ry * Math.cos(a)) / speed;
|
|
5297
|
+
}
|
|
5298
|
+
const dx = px[i] - ox[i];
|
|
5299
|
+
const dy = py[i] - oy[i];
|
|
5300
|
+
const sine = Math.abs(tx * dy - ty * dx) / Math.hypot(dx, dy);
|
|
5301
|
+
let left = SEAM / Math.max(0.25, sine);
|
|
5302
|
+
let at = a;
|
|
5303
|
+
for (let n = 0; q && n < 8 && left > 0 && at !== end; n += 1) {
|
|
5304
|
+
const speed = Math.hypot(q.rx * Math.sin(at), q.ry * Math.cos(at));
|
|
5305
|
+
const turn = Math.abs(end - at);
|
|
5306
|
+
if (left >= turn * speed) {
|
|
5307
|
+
left -= turn * speed;
|
|
5308
|
+
at = end;
|
|
5309
|
+
} else {
|
|
5310
|
+
at += (sign * left) / speed;
|
|
5311
|
+
left = 0;
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5314
|
+
return [at, at === end ? Math.min(left, room) : 0];
|
|
5315
|
+
};
|
|
5316
|
+
const points: number[] = [];
|
|
5317
|
+
/** The point at an angle on a quarter, or the corner where it is square,
|
|
5318
|
+
* and `over` on past the quarter's end along the edge `along`. */
|
|
5319
|
+
const point = (
|
|
5320
|
+
q: Quarter,
|
|
5321
|
+
a: number,
|
|
5322
|
+
cx: number,
|
|
5323
|
+
cy: number,
|
|
5324
|
+
over = 0,
|
|
5325
|
+
along: readonly number[] = ALONG[0],
|
|
5326
|
+
): void => {
|
|
5327
|
+
const x = (q ? q.cx + q.rx * Math.cos(a) : cx) + over * along[0];
|
|
5328
|
+
const y = (q ? q.cy + q.ry * Math.sin(a) : cy) + over * along[1];
|
|
5329
|
+
const n = points.length;
|
|
5330
|
+
if (n && points[n - 2] === x && points[n - 1] === y) return;
|
|
5331
|
+
points.push(x, y);
|
|
5332
|
+
};
|
|
5333
|
+
/** A quarter's curve from one angle to another, as chords, after the
|
|
5334
|
+
* point it starts at. */
|
|
5335
|
+
const sweep = (q: Quarter, from: number, to: number): void => {
|
|
5336
|
+
if (!q || from === to) return;
|
|
5337
|
+
const radius = Math.max(q.rx, q.ry);
|
|
5338
|
+
const step =
|
|
5339
|
+
radius > CURVE_TOLERANCE
|
|
5340
|
+
? 2 * Math.acos(1 - CURVE_TOLERANCE / radius)
|
|
5341
|
+
: QUARTER;
|
|
5342
|
+
const n = Math.min(64, Math.ceil(Math.abs(to - from) / step));
|
|
5343
|
+
for (let k = 1; k <= n; k += 1) {
|
|
5344
|
+
point(q, from + ((to - from) * k) / n, 0, 0);
|
|
5345
|
+
}
|
|
5346
|
+
};
|
|
5347
|
+
for (let k = 0; k < runs.length; k += 1) {
|
|
5348
|
+
const { from: i, to: j, ink } = runs[k];
|
|
5349
|
+
if (ink === null) continue;
|
|
5350
|
+
// Where the share starts and ends on each edge. Two antialiased fills
|
|
5351
|
+
// that meet on a line each cover half of the pixels along it, and what
|
|
5352
|
+
// is under the border shows through between them. So a share reaches
|
|
5353
|
+
// on under the one drawn after it and covering it, and that one's
|
|
5354
|
+
// edge is the cut, over it — as Blink overdraws a corner, and as the
|
|
5355
|
+
// square corners are drawn.
|
|
5356
|
+
let [so, soOver] = [cutOuter[i], 0];
|
|
5357
|
+
let [si, siOver] = [cutInner[i], 0];
|
|
5358
|
+
let [eo, eoOver] = [cutOuter[j], 0];
|
|
5359
|
+
let [ei, eiOver] = [cutInner[j], 0];
|
|
5360
|
+
const back = (i + 3) % 4;
|
|
5361
|
+
if (under(k, i)) {
|
|
5362
|
+
[so, soOver] = past(outer[i], i, so, false, outerRoom[back]);
|
|
5363
|
+
[si, siOver] = past(inner[i], i, si, false, innerRoom[back]);
|
|
5364
|
+
}
|
|
5365
|
+
if (under(k, j)) {
|
|
5366
|
+
[eo, eoOver] = past(outer[j], j, eo, true, outerRoom[j]);
|
|
5367
|
+
[ei, eiOver] = past(inner[j], j, ei, true, innerRoom[j]);
|
|
5368
|
+
}
|
|
5369
|
+
const onward = ALONG[(j + 1) % 4];
|
|
5370
|
+
const backward = [-ALONG[i][0], -ALONG[i][1]];
|
|
5371
|
+
points.length = 0;
|
|
5372
|
+
// along the border edge, clockwise
|
|
5373
|
+
point(outer[i], so, ox[i], oy[i], soOver, backward);
|
|
5374
|
+
point(outer[i], so, ox[i], oy[i]);
|
|
5375
|
+
sweep(outer[i], so, start(i) + QUARTER);
|
|
5376
|
+
for (let c = (i + 1) % 4; ; c = (c + 1) % 4) {
|
|
5377
|
+
point(outer[c], start(c), ox[c], oy[c]);
|
|
5378
|
+
if (c === j) break;
|
|
5379
|
+
sweep(outer[c], start(c), start(c) + QUARTER);
|
|
5380
|
+
}
|
|
5381
|
+
sweep(outer[j], start(j), eo);
|
|
5382
|
+
point(outer[j], eo, ox[j], oy[j], eoOver, onward);
|
|
5383
|
+
// and back along the padding edge
|
|
5384
|
+
point(inner[j], ei, px[j], py[j], eiOver, onward);
|
|
5385
|
+
point(inner[j], ei, px[j], py[j]);
|
|
5386
|
+
sweep(inner[j], ei, start(j));
|
|
5387
|
+
for (let c = (j + 3) % 4; ; c = (c + 3) % 4) {
|
|
5388
|
+
point(inner[c], start(c) + QUARTER, px[c], py[c]);
|
|
5389
|
+
if (c === i) break;
|
|
5390
|
+
sweep(inner[c], start(c) + QUARTER, start(c));
|
|
5391
|
+
}
|
|
5392
|
+
sweep(inner[i], start(i) + QUARTER, si);
|
|
5393
|
+
point(inner[i], si, px[i], py[i], siOver, backward);
|
|
5394
|
+
ctx.fillStyle = ink;
|
|
5395
|
+
fillPolygon(ctx, points, area);
|
|
5396
|
+
}
|
|
4980
5397
|
return true;
|
|
4981
5398
|
}
|
|
4982
5399
|
|
|
@@ -4994,10 +5411,10 @@ interface Rounded {
|
|
|
4994
5411
|
* the box first and insetting the cut by the borders put the hole a
|
|
4995
5412
|
* border's width in from the cut rather than from the box's edge, and left
|
|
4996
5413
|
* none where a border was wider than `CLAMP_PAD`: the ring filled whatever
|
|
4997
|
-
* of the box a paint reached.
|
|
4998
|
-
*
|
|
4999
|
-
*
|
|
5000
|
-
*
|
|
5414
|
+
* of the box a paint reached. The window is the border edge's
|
|
5415
|
+
* (`roundedWindow`), whose curves the padding edge's run inside, and each
|
|
5416
|
+
* edge is cut to it as `cutRounded` cuts one. Null where the ring has
|
|
5417
|
+
* nothing in the window.
|
|
5001
5418
|
*/
|
|
5002
5419
|
function cutRing(
|
|
5003
5420
|
options: PaintOptions,
|
|
@@ -5011,77 +5428,101 @@ function cutRing(
|
|
|
5011
5428
|
bottom: number,
|
|
5012
5429
|
left: number,
|
|
5013
5430
|
): { outer: Rounded; inner: Rounded | null } | null {
|
|
5014
|
-
const {
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
const across: [number, number][] = [
|
|
5018
|
-
[x, x + cx[0]],
|
|
5019
|
-
[x + w - cx[1], x + w],
|
|
5020
|
-
[x + w - cx[2], x + w],
|
|
5021
|
-
[x, x + cx[3]],
|
|
5022
|
-
];
|
|
5023
|
-
const down: [number, number][] = [
|
|
5024
|
-
[y, y + cy[0]],
|
|
5025
|
-
[y, y + cy[1]],
|
|
5026
|
-
[y + h - cy[2], y + h],
|
|
5027
|
-
[y + h - cy[3], y + h],
|
|
5028
|
-
];
|
|
5029
|
-
const d = options.damage;
|
|
5030
|
-
const side = (at: number, spans: [number, number][], towards: number) =>
|
|
5031
|
-
Math.max(
|
|
5032
|
-
-COORD_LIMIT,
|
|
5033
|
-
Math.min(COORD_LIMIT, outOfSpans(at, spans, towards)),
|
|
5034
|
-
);
|
|
5035
|
-
const x0 = side(d ? d.x - CLAMP_PAD : -COORD_LIMIT, across, -1);
|
|
5036
|
-
const y0 = side(d ? d.y - CLAMP_PAD : -COORD_LIMIT, down, -1);
|
|
5037
|
-
const x1 = side(d ? d.x + d.width + CLAMP_PAD : COORD_LIMIT, across, 1);
|
|
5038
|
-
const y1 = side(d ? d.y + d.height + CLAMP_PAD : COORD_LIMIT, down, 1);
|
|
5039
|
-
const cut = (
|
|
5040
|
-
rx: number,
|
|
5041
|
-
ry: number,
|
|
5042
|
-
rw: number,
|
|
5043
|
-
rh: number,
|
|
5044
|
-
c: Corners,
|
|
5045
|
-
): Rounded | null => {
|
|
5046
|
-
const l = Math.max(rx, x0);
|
|
5047
|
-
const t = Math.max(ry, y0);
|
|
5048
|
-
const r = Math.min(rx + rw, x1);
|
|
5049
|
-
const b = Math.min(ry + rh, y1);
|
|
5050
|
-
if (r <= l || b <= t) return null;
|
|
5051
|
-
// the corners of the sides the window left where they were
|
|
5052
|
-
const kept = [
|
|
5053
|
-
t === ry && l === rx,
|
|
5054
|
-
t === ry && r === rx + rw,
|
|
5055
|
-
b === ry + rh && r === rx + rw,
|
|
5056
|
-
b === ry + rh && l === rx,
|
|
5057
|
-
];
|
|
5058
|
-
return {
|
|
5059
|
-
rect: { x: l, y: t, width: r - l, height: b - t },
|
|
5060
|
-
corners: {
|
|
5061
|
-
x: c.x.map((v, i) => (kept[i] ? v : 0)) as Corners['x'],
|
|
5062
|
-
y: c.y.map((v, i) => (kept[i] ? v : 0)) as Corners['y'],
|
|
5063
|
-
},
|
|
5064
|
-
};
|
|
5065
|
-
};
|
|
5066
|
-
const outer = cut(x, y, w, h, corners);
|
|
5431
|
+
const edge = { rect: { x, y, width: w, height: h }, corners };
|
|
5432
|
+
const cut = roundedWindow(options, [edge]);
|
|
5433
|
+
const outer = cutRounded(cut, edge);
|
|
5067
5434
|
if (!outer) return null;
|
|
5068
5435
|
const iw = w - left - right;
|
|
5069
5436
|
const ih = h - top - bottom;
|
|
5070
5437
|
const inner =
|
|
5071
5438
|
iw > 0 && ih > 0
|
|
5072
|
-
? cut
|
|
5073
|
-
x + left,
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
ih,
|
|
5077
|
-
insetCorners(corners, top, right, bottom, left),
|
|
5078
|
-
)
|
|
5439
|
+
? cutRounded(cut, {
|
|
5440
|
+
rect: { x: x + left, y: y + top, width: iw, height: ih },
|
|
5441
|
+
corners: insetCorners(corners, top, right, bottom, left),
|
|
5442
|
+
})
|
|
5079
5443
|
: null;
|
|
5080
5444
|
// a window inside the padding edge, where the ring has nothing
|
|
5081
5445
|
if (inner && sameRounded(outer, inner)) return null;
|
|
5082
5446
|
return { outer, inner };
|
|
5083
5447
|
}
|
|
5084
5448
|
|
|
5449
|
+
/** Where a paint is cut to (`roundedWindow`): left, top, right, bottom. */
|
|
5450
|
+
type Cut = [number, number, number, number];
|
|
5451
|
+
|
|
5452
|
+
/**
|
|
5453
|
+
* The window `clampRect` cuts to, `pad` round what the paint reaches, for
|
|
5454
|
+
* rectangles with rounded corners: a cut is straight, so a side of it is
|
|
5455
|
+
* moved out past any corner's curve it would cross, in any of them.
|
|
5456
|
+
*/
|
|
5457
|
+
function roundedWindow(
|
|
5458
|
+
options: PaintOptions,
|
|
5459
|
+
shapes: readonly Rounded[],
|
|
5460
|
+
pad = CLAMP_PAD,
|
|
5461
|
+
): Cut {
|
|
5462
|
+
// where each corner's curve runs, across and down
|
|
5463
|
+
const across: [number, number][] = [];
|
|
5464
|
+
const down: [number, number][] = [];
|
|
5465
|
+
for (const { rect, corners } of shapes) {
|
|
5466
|
+
const { x, y, width: w, height: h } = rect;
|
|
5467
|
+
const { x: cx, y: cy } = corners;
|
|
5468
|
+
across.push(
|
|
5469
|
+
[x, x + cx[0]],
|
|
5470
|
+
[x + w - cx[1], x + w],
|
|
5471
|
+
[x + w - cx[2], x + w],
|
|
5472
|
+
[x, x + cx[3]],
|
|
5473
|
+
);
|
|
5474
|
+
down.push(
|
|
5475
|
+
[y, y + cy[0]],
|
|
5476
|
+
[y, y + cy[1]],
|
|
5477
|
+
[y + h - cy[2], y + h],
|
|
5478
|
+
[y + h - cy[3], y + h],
|
|
5479
|
+
);
|
|
5480
|
+
}
|
|
5481
|
+
const d = options.damage;
|
|
5482
|
+
const side = (at: number, spans: [number, number][], towards: number) =>
|
|
5483
|
+
Math.max(
|
|
5484
|
+
-COORD_LIMIT,
|
|
5485
|
+
Math.min(COORD_LIMIT, outOfSpans(at, spans, towards)),
|
|
5486
|
+
);
|
|
5487
|
+
return [
|
|
5488
|
+
side(d ? d.x - pad : -COORD_LIMIT, across, -1),
|
|
5489
|
+
side(d ? d.y - pad : -COORD_LIMIT, down, -1),
|
|
5490
|
+
side(d ? d.x + d.width + pad : COORD_LIMIT, across, 1),
|
|
5491
|
+
side(d ? d.y + d.height + pad : COORD_LIMIT, down, 1),
|
|
5492
|
+
];
|
|
5493
|
+
}
|
|
5494
|
+
|
|
5495
|
+
/**
|
|
5496
|
+
* A rounded rectangle cut to a window (`roundedWindow`), as `clampRect`
|
|
5497
|
+
* cuts a rectangle: a corner on a side the window cut is square, its
|
|
5498
|
+
* curve being outside, and the rest are whole. Null where none of it is in
|
|
5499
|
+
* the window.
|
|
5500
|
+
*/
|
|
5501
|
+
function cutRounded(
|
|
5502
|
+
[x0, y0, x1, y1]: Cut,
|
|
5503
|
+
{ rect: { x, y, width: w, height: h }, corners: c }: Rounded,
|
|
5504
|
+
): Rounded | null {
|
|
5505
|
+
const l = Math.max(x, x0);
|
|
5506
|
+
const t = Math.max(y, y0);
|
|
5507
|
+
const r = Math.min(x + w, x1);
|
|
5508
|
+
const b = Math.min(y + h, y1);
|
|
5509
|
+
if (r <= l || b <= t) return null;
|
|
5510
|
+
// the corners of the sides the window left where they were
|
|
5511
|
+
const kept = [
|
|
5512
|
+
t === y && l === x,
|
|
5513
|
+
t === y && r === x + w,
|
|
5514
|
+
b === y + h && r === x + w,
|
|
5515
|
+
b === y + h && l === x,
|
|
5516
|
+
];
|
|
5517
|
+
return {
|
|
5518
|
+
rect: { x: l, y: t, width: r - l, height: b - t },
|
|
5519
|
+
corners: {
|
|
5520
|
+
x: c.x.map((v, i) => (kept[i] ? v : 0)) as Corners['x'],
|
|
5521
|
+
y: c.y.map((v, i) => (kept[i] ? v : 0)) as Corners['y'],
|
|
5522
|
+
},
|
|
5523
|
+
};
|
|
5524
|
+
}
|
|
5525
|
+
|
|
5085
5526
|
/** A position moved out of any of `spans` it is inside, towards their
|
|
5086
5527
|
* starts where `towards` is negative and their ends where it is not. */
|
|
5087
5528
|
function outOfSpans(
|
|
@@ -5406,10 +5847,9 @@ function paintCollapsedBorders(
|
|
|
5406
5847
|
);
|
|
5407
5848
|
for (const s of segments) {
|
|
5408
5849
|
if (isTransparent(s.border.color)) continue;
|
|
5409
|
-
|
|
5410
|
-
if (!rect) continue;
|
|
5850
|
+
if (!clampRect(options, s.x, s.y, s.w, s.h)) continue;
|
|
5411
5851
|
ctx.fillStyle = s.border.color;
|
|
5412
|
-
fillEdge(ctx,
|
|
5852
|
+
fillEdge(ctx, options, s.x, s.y, s.w, s.h, s.border.style, s.horizontal);
|
|
5413
5853
|
}
|
|
5414
5854
|
}
|
|
5415
5855
|
|
|
@@ -5471,12 +5911,8 @@ function fillSide(
|
|
|
5471
5911
|
}
|
|
5472
5912
|
const ex = at === 1 ? x + w - r : x;
|
|
5473
5913
|
const ey = at === 0 ? y : at === 2 ? y + h - b : y + t;
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
: clampRect(options, ex, ey, width, h - t - b);
|
|
5477
|
-
// The un-clamped start is the dash phase's origin, so the pattern does
|
|
5478
|
-
// not crawl as the viewport moves along a long edge.
|
|
5479
|
-
if (rect) fillEdge(ctx, rect, horizontal ? ex : ey, style, horizontal);
|
|
5914
|
+
if (horizontal) fillEdge(ctx, options, ex, ey, w, width, style, true);
|
|
5915
|
+
else fillEdge(ctx, options, ex, ey, width, h - t - b, style, false);
|
|
5480
5916
|
}
|
|
5481
5917
|
|
|
5482
5918
|
/**
|
|
@@ -5518,45 +5954,67 @@ function doubleBand(thickness: number): number {
|
|
|
5518
5954
|
return Math.max(1, Math.floor(thickness / 3));
|
|
5519
5955
|
}
|
|
5520
5956
|
|
|
5957
|
+
/**
|
|
5958
|
+
* A side at `x, y`, `w` by `h`, running across when `horizontal`, as the
|
|
5959
|
+
* rectangles its style makes, each cut to what the paint reaches. The
|
|
5960
|
+
* dashes, the dots and a double side's lines are measured from the side
|
|
5961
|
+
* and cut after: measured from the cut, a side wider than `CLAMP_PAD` had
|
|
5962
|
+
* dashes as long and as far apart as the cut was wide, starting wherever
|
|
5963
|
+
* it started, and a strip repainted down one dash was striped across it.
|
|
5964
|
+
*/
|
|
5521
5965
|
function fillEdge(
|
|
5522
5966
|
ctx: Pick<FillContext, 'fillRect'>,
|
|
5523
|
-
|
|
5524
|
-
|
|
5967
|
+
options: PaintOptions,
|
|
5968
|
+
x: number,
|
|
5969
|
+
y: number,
|
|
5970
|
+
w: number,
|
|
5971
|
+
h: number,
|
|
5525
5972
|
style: ComputedStyle['borderTopStyle'],
|
|
5526
5973
|
horizontal: boolean,
|
|
5527
5974
|
): void {
|
|
5528
|
-
const { x, y, w, h } = rect;
|
|
5529
5975
|
const length = horizontal ? w : h;
|
|
5530
5976
|
const thickness = horizontal ? h : w;
|
|
5531
|
-
if (length
|
|
5977
|
+
if (!(length > 0 && thickness > 0)) return;
|
|
5978
|
+
const fill = (fx: number, fy: number, fw: number, fh: number): void => {
|
|
5979
|
+
const rect = clampRect(options, fx, fy, fw, fh);
|
|
5980
|
+
if (rect) ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
|
|
5981
|
+
};
|
|
5532
5982
|
if (style === 'dashed' || style === 'dotted') {
|
|
5983
|
+
// what of the side the paint reaches, so that a side far longer than
|
|
5984
|
+
// that is not walked from its start
|
|
5985
|
+
const cut = clampRect(options, x, y, w, h);
|
|
5986
|
+
if (!cut) return;
|
|
5533
5987
|
const period = style === 'dotted' ? thickness * 2 : thickness * 3;
|
|
5534
5988
|
const on = style === 'dotted' ? thickness : thickness * 2;
|
|
5535
|
-
const
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5989
|
+
const start = horizontal ? x : y;
|
|
5990
|
+
const end = start + length;
|
|
5991
|
+
const from = horizontal ? cut.x : cut.y;
|
|
5992
|
+
const to = from + (horizontal ? cut.w : cut.h);
|
|
5993
|
+
// from the side's start, so the dash the paint cuts into is the one it
|
|
5994
|
+
// always was
|
|
5995
|
+
for (
|
|
5996
|
+
let i = start + Math.floor((from - start) / period) * period;
|
|
5997
|
+
i < to;
|
|
5998
|
+
i += period
|
|
5999
|
+
) {
|
|
6000
|
+
const run = Math.min(i + on, end) - i;
|
|
6001
|
+
if (horizontal) fill(i, y, run, thickness);
|
|
6002
|
+
else fill(x, i, thickness, run);
|
|
5545
6003
|
}
|
|
5546
6004
|
return;
|
|
5547
6005
|
}
|
|
5548
6006
|
if (style === 'double' && thickness >= 3) {
|
|
5549
6007
|
const band = doubleBand(thickness);
|
|
5550
6008
|
if (horizontal) {
|
|
5551
|
-
|
|
5552
|
-
|
|
6009
|
+
fill(x, y, length, band);
|
|
6010
|
+
fill(x, y + thickness - band, length, band);
|
|
5553
6011
|
} else {
|
|
5554
|
-
|
|
5555
|
-
|
|
6012
|
+
fill(x, y, band, length);
|
|
6013
|
+
fill(x + thickness - band, y, band, length);
|
|
5556
6014
|
}
|
|
5557
6015
|
return;
|
|
5558
6016
|
}
|
|
5559
|
-
|
|
6017
|
+
fill(x, y, w, h);
|
|
5560
6018
|
}
|
|
5561
6019
|
|
|
5562
6020
|
/** A list item's bullet or number, in the margin. */
|
|
@@ -6021,7 +6479,7 @@ function paintClippedText(
|
|
|
6021
6479
|
) +
|
|
6022
6480
|
2;
|
|
6023
6481
|
for (const run of natural.runs) {
|
|
6024
|
-
const clip = clipBoxOf(text.spans.boxAt?.(run.start) ?? null
|
|
6482
|
+
const clip = clipBoxOf(text.spans.boxAt?.(run.start) ?? null);
|
|
6025
6483
|
if (!clip) continue;
|
|
6026
6484
|
let byLayout = parts.get(clip);
|
|
6027
6485
|
if (!byLayout) parts.set(clip, (byLayout = new Map()));
|
|
@@ -6043,7 +6501,7 @@ function paintClippedText(
|
|
|
6043
6501
|
const list = [...byLayout.values()];
|
|
6044
6502
|
const style = clip.style;
|
|
6045
6503
|
let area: Rect;
|
|
6046
|
-
if (clip
|
|
6504
|
+
if (clip.kind !== 'inline') area = paddingBox(clip, options);
|
|
6047
6505
|
else {
|
|
6048
6506
|
// an inline box: its runs, and its padding round them
|
|
6049
6507
|
let x0 = Infinity;
|