@react-x11/components 0.14.0 → 0.14.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/html/css/cascade.d.ts +42 -1
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +310 -12
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/shapes.d.ts +44 -0
- package/dist/html/css/shapes.d.ts.map +1 -0
- package/dist/html/css/shapes.js +194 -0
- package/dist/html/css/shapes.js.map +1 -0
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +1 -15
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/layout/block.d.ts +16 -5
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +26 -5
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +13 -0
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +18 -0
- 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 +58 -10
- package/dist/html/layout/css-grid.js.map +1 -1
- package/dist/html/layout/flex.js +19 -6
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +38 -16
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +1 -0
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +57 -12
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +4 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +269 -84
- package/dist/html/paint.js.map +1 -1
- package/dist/html/svg.d.ts +27 -10
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +259 -46
- package/dist/html/svg.js.map +1 -1
- package/package.json +1 -1
- package/src/html/css/cascade.ts +343 -8
- package/src/html/css/shapes.ts +239 -0
- package/src/html/css/style.ts +1 -13
- package/src/html/layout/block.ts +27 -5
- package/src/html/layout/boxes.ts +30 -0
- package/src/html/layout/css-grid.ts +77 -13
- package/src/html/layout/flex.ts +22 -9
- package/src/html/layout/table.ts +45 -18
- package/src/html/node.ts +54 -11
- package/src/html/paint.ts +379 -90
- package/src/html/svg.ts +318 -39
package/src/html/paint.ts
CHANGED
|
@@ -88,6 +88,7 @@ import type {
|
|
|
88
88
|
LineText,
|
|
89
89
|
Marker,
|
|
90
90
|
SelectionStyler,
|
|
91
|
+
ShapeStyler,
|
|
91
92
|
} from './layout/boxes.js';
|
|
92
93
|
import type { SelectionStyle } from './css/cascade.js';
|
|
93
94
|
import {
|
|
@@ -247,6 +248,9 @@ export interface PaintOptions {
|
|
|
247
248
|
/** @internal Each box's `::selection`, where a rule styles one
|
|
248
249
|
* (`BoxTree.selectionStyler`). */
|
|
249
250
|
selectionStyler?: SelectionStyler | null;
|
|
251
|
+
/** @internal What the rules give the shapes in each drawing, where one
|
|
252
|
+
* could reach any (`BoxTree.shapeStyler`). */
|
|
253
|
+
shapeStyler?: ShapeStyler | null;
|
|
250
254
|
}
|
|
251
255
|
|
|
252
256
|
/**
|
|
@@ -705,6 +709,7 @@ export function paintDocument(
|
|
|
705
709
|
canvasSource: canvas?.source,
|
|
706
710
|
negative: tree.negative,
|
|
707
711
|
selectionStyler: options.selection ? tree.selectionStyler : null,
|
|
712
|
+
shapeStyler: tree.shapeStyler,
|
|
708
713
|
});
|
|
709
714
|
ctx.restore();
|
|
710
715
|
}
|
|
@@ -1535,7 +1540,9 @@ function paintContent(
|
|
|
1535
1540
|
for (const child of below) paintStacked(ctx, child, options);
|
|
1536
1541
|
}
|
|
1537
1542
|
if (visible) {
|
|
1538
|
-
if (box.marker)
|
|
1543
|
+
if (box.marker) {
|
|
1544
|
+
paintMarker(ctx, box.marker, options, box.style.colorScheme);
|
|
1545
|
+
}
|
|
1539
1546
|
if (box.replaced === 'image') paintImage(ctx, box, options);
|
|
1540
1547
|
else if (box.replaced === 'svg') paintSvg(ctx, box, options);
|
|
1541
1548
|
}
|
|
@@ -2250,7 +2257,7 @@ function paintFlowLines(
|
|
|
2250
2257
|
}
|
|
2251
2258
|
if (!intersects(child, options)) continue;
|
|
2252
2259
|
if (child.marker && child.style.visibility === 'visible') {
|
|
2253
|
-
paintMarker(ctx, child.marker, options);
|
|
2260
|
+
paintMarker(ctx, child.marker, options, child.style.colorScheme);
|
|
2254
2261
|
}
|
|
2255
2262
|
if (child.lines) paintLines(ctx, child, options);
|
|
2256
2263
|
paintFlowLines(ctx, child, options, outlines);
|
|
@@ -4230,11 +4237,20 @@ function paintBackgroundImage(
|
|
|
4230
4237
|
// a drawing is drawn a tile at a time, at the size it was given
|
|
4231
4238
|
if (tiles <= MAX_TILES) {
|
|
4232
4239
|
for (let y = fromY; y < toY; y += stepY) {
|
|
4233
|
-
for (let x = fromX; x < toX; x += stepX)
|
|
4234
|
-
svg.
|
|
4240
|
+
for (let x = fromX; x < toX; x += stepX) {
|
|
4241
|
+
svg.drawImage(
|
|
4242
|
+
ctx,
|
|
4243
|
+
Math.round(x),
|
|
4244
|
+
Math.round(y),
|
|
4245
|
+
iw,
|
|
4246
|
+
ih,
|
|
4247
|
+
scale,
|
|
4248
|
+
style.colorScheme,
|
|
4249
|
+
);
|
|
4250
|
+
}
|
|
4235
4251
|
}
|
|
4236
4252
|
} else {
|
|
4237
|
-
svg.
|
|
4253
|
+
svg.drawImage(ctx, x0, y0, iw, ih, scale, style.colorScheme);
|
|
4238
4254
|
}
|
|
4239
4255
|
} else if (
|
|
4240
4256
|
tiles > 1 &&
|
|
@@ -4478,7 +4494,8 @@ function paintOutline(
|
|
|
4478
4494
|
* width: the CSS triangle a dropdown's caret and a tooltip's arrow are
|
|
4479
4495
|
* drawn with, which came out as the rectangle around it. Only a solid side
|
|
4480
4496
|
* is cut, and only against a side that is solid or paints nothing; dots,
|
|
4481
|
-
* dashes and a double border's two lines keep their rectangles
|
|
4497
|
+
* dashes and a double border's two lines keep their rectangles, the lines
|
|
4498
|
+
* joined where two double sides of one colour meet (`fillSide`).
|
|
4482
4499
|
*/
|
|
4483
4500
|
function paintBorders(
|
|
4484
4501
|
ctx: PaintContext,
|
|
@@ -4523,21 +4540,16 @@ function paintBorders(
|
|
|
4523
4540
|
paths && mitred(b, r, bottomInk, rightInk, bottomStyle, rightStyle);
|
|
4524
4541
|
const bl = paths && mitred(b, l, bottomInk, leftInk, bottomStyle, leftStyle);
|
|
4525
4542
|
|
|
4543
|
+
const widths = [t, r, b, l];
|
|
4544
|
+
const joins = doubleJoins(box);
|
|
4545
|
+
/** A side with square corners, the top first and clockwise. */
|
|
4526
4546
|
const edge = (
|
|
4527
|
-
|
|
4528
|
-
ey: number,
|
|
4529
|
-
ew: number,
|
|
4530
|
-
eh: number,
|
|
4547
|
+
at: number,
|
|
4531
4548
|
style: ComputedStyle['borderTopStyle'],
|
|
4532
4549
|
color: string,
|
|
4533
|
-
horizontal: boolean,
|
|
4534
4550
|
): void => {
|
|
4535
|
-
const rect = clampRect(options, ex, ey, ew, eh);
|
|
4536
|
-
if (!rect) return;
|
|
4537
4551
|
ctx.fillStyle = color;
|
|
4538
|
-
|
|
4539
|
-
// not crawl as the viewport moves along a long edge.
|
|
4540
|
-
fillEdge(ctx, rect, horizontal ? ex : ey, style, horizontal);
|
|
4552
|
+
fillSide(ctx, options, at, x, y, w, h, widths, style, joins);
|
|
4541
4553
|
};
|
|
4542
4554
|
/** A side with a cut corner: its outer edge and then its inner one. */
|
|
4543
4555
|
const side = (points: number[], color: string): void => {
|
|
@@ -4568,7 +4580,7 @@ function paintBorders(
|
|
|
4568
4580
|
[x, y, x1, y, cutRight ? px1 : x1, py0, cutLeft ? px0 : x, py0],
|
|
4569
4581
|
topInk,
|
|
4570
4582
|
);
|
|
4571
|
-
} else edge(
|
|
4583
|
+
} else edge(0, topStyle, topInk);
|
|
4572
4584
|
}
|
|
4573
4585
|
if (bottomInk !== null) {
|
|
4574
4586
|
const cutLeft = bl && !over(leftInk);
|
|
@@ -4578,17 +4590,17 @@ function paintBorders(
|
|
|
4578
4590
|
[x1, y1, x, y1, cutLeft ? px0 : x, py1, cutRight ? px1 : x1, py1],
|
|
4579
4591
|
bottomInk,
|
|
4580
4592
|
);
|
|
4581
|
-
} else edge(
|
|
4593
|
+
} else edge(2, bottomStyle, bottomInk);
|
|
4582
4594
|
}
|
|
4583
4595
|
if (leftInk !== null) {
|
|
4584
4596
|
if (tl || bl) {
|
|
4585
4597
|
side([x, bl ? y1 : py1, x, tl ? y : py0, px0, py0, px0, py1], leftInk);
|
|
4586
|
-
} else edge(
|
|
4598
|
+
} else edge(3, leftStyle, leftInk);
|
|
4587
4599
|
}
|
|
4588
4600
|
if (rightInk !== null) {
|
|
4589
4601
|
if (tr || br) {
|
|
4590
4602
|
side([x1, tr ? y : py0, x1, br ? y1 : py1, px1, py1, px1, py0], rightInk);
|
|
4591
|
-
} else edge(
|
|
4603
|
+
} else edge(1, rightStyle, rightInk);
|
|
4592
4604
|
}
|
|
4593
4605
|
}
|
|
4594
4606
|
|
|
@@ -4715,12 +4727,13 @@ function paintBorderImage(
|
|
|
4715
4727
|
const svg = image instanceof SvgDrawing ? image : null;
|
|
4716
4728
|
const w = Math.max(1, Math.round(cw));
|
|
4717
4729
|
const h = Math.max(1, Math.round(ch));
|
|
4730
|
+
const scheme = box.style.colorScheme;
|
|
4718
4731
|
const drawn = options.cached?.(
|
|
4719
|
-
`border-image|${key}|${w}x${h}`,
|
|
4732
|
+
`border-image|${key}|${w}x${h}|${scheme}`,
|
|
4720
4733
|
w,
|
|
4721
4734
|
h,
|
|
4722
4735
|
(sctx) => {
|
|
4723
|
-
if (svg) svg.
|
|
4736
|
+
if (svg) svg.drawImage(sctx, 0, 0, w, h, scale, scheme);
|
|
4724
4737
|
else if (typeof source !== 'string') {
|
|
4725
4738
|
const paint = gradientFill(sctx, source, 0, 0, w, h, box.style.color);
|
|
4726
4739
|
if (paint) fillGradient(sctx, paint, { x: 0, y: 0, w, h });
|
|
@@ -4942,28 +4955,164 @@ function roundedRing(
|
|
|
4942
4955
|
}
|
|
4943
4956
|
if (color === null) return true;
|
|
4944
4957
|
if (isTransparent(color)) return true;
|
|
4945
|
-
const
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4958
|
+
const ring = cutRing(
|
|
4959
|
+
options,
|
|
4960
|
+
x,
|
|
4961
|
+
y,
|
|
4962
|
+
w,
|
|
4963
|
+
h,
|
|
4964
|
+
corners,
|
|
4965
|
+
box.borderTop,
|
|
4966
|
+
box.borderRight,
|
|
4967
|
+
box.borderBottom,
|
|
4968
|
+
box.borderLeft,
|
|
4969
|
+
);
|
|
4970
|
+
if (!ring) return true;
|
|
4971
|
+
const { outer, inner } = ring;
|
|
4951
4972
|
ctx.fillStyle = inkColor(color, s.color);
|
|
4952
4973
|
fillRing(
|
|
4953
4974
|
ctx,
|
|
4954
|
-
|
|
4955
|
-
corners,
|
|
4956
|
-
{
|
|
4957
|
-
|
|
4958
|
-
y: rect.y + top,
|
|
4959
|
-
width: Math.max(0, rect.w - left - right),
|
|
4960
|
-
height: Math.max(0, rect.h - top - bottom),
|
|
4961
|
-
},
|
|
4962
|
-
insetCorners(corners, top, right, bottom, left),
|
|
4975
|
+
outer.rect,
|
|
4976
|
+
outer.corners,
|
|
4977
|
+
inner ? inner.rect : { x: 0, y: 0, width: 0, height: 0 },
|
|
4978
|
+
inner ? inner.corners : outer.corners,
|
|
4963
4979
|
);
|
|
4964
4980
|
return true;
|
|
4965
4981
|
}
|
|
4966
4982
|
|
|
4983
|
+
/** A rectangle and the radii of its corners. */
|
|
4984
|
+
interface Rounded {
|
|
4985
|
+
rect: Rect;
|
|
4986
|
+
corners: Corners;
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4989
|
+
/**
|
|
4990
|
+
* A rounded box's ring — its border edge and its padding edge, the box
|
|
4991
|
+
* inside its borders — cut to the neighbourhood of what is being painted,
|
|
4992
|
+
* as `clampRect` cuts a rectangle. Both edges are the box's own, cut to
|
|
4993
|
+
* one window, so what is left is the part of the true ring in it. Cutting
|
|
4994
|
+
* the box first and insetting the cut by the borders put the hole a
|
|
4995
|
+
* border's width in from the cut rather than from the box's edge, and left
|
|
4996
|
+
* none where a border was wider than `CLAMP_PAD`: the ring filled whatever
|
|
4997
|
+
* of the box a paint reached. A cut is straight, so a side of the window
|
|
4998
|
+
* is moved out past any corner's curve it would cross, and a corner on a
|
|
4999
|
+
* side the window cut is square, its curve being outside. Null where the
|
|
5000
|
+
* ring has nothing in the window.
|
|
5001
|
+
*/
|
|
5002
|
+
function cutRing(
|
|
5003
|
+
options: PaintOptions,
|
|
5004
|
+
x: number,
|
|
5005
|
+
y: number,
|
|
5006
|
+
w: number,
|
|
5007
|
+
h: number,
|
|
5008
|
+
corners: Corners,
|
|
5009
|
+
top: number,
|
|
5010
|
+
right: number,
|
|
5011
|
+
bottom: number,
|
|
5012
|
+
left: number,
|
|
5013
|
+
): { outer: Rounded; inner: Rounded | null } | null {
|
|
5014
|
+
const { x: cx, y: cy } = corners;
|
|
5015
|
+
// where each corner's curve runs, across and down — the padding edge's
|
|
5016
|
+
// run inside the border edge's
|
|
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);
|
|
5067
|
+
if (!outer) return null;
|
|
5068
|
+
const iw = w - left - right;
|
|
5069
|
+
const ih = h - top - bottom;
|
|
5070
|
+
const inner =
|
|
5071
|
+
iw > 0 && ih > 0
|
|
5072
|
+
? cut(
|
|
5073
|
+
x + left,
|
|
5074
|
+
y + top,
|
|
5075
|
+
iw,
|
|
5076
|
+
ih,
|
|
5077
|
+
insetCorners(corners, top, right, bottom, left),
|
|
5078
|
+
)
|
|
5079
|
+
: null;
|
|
5080
|
+
// a window inside the padding edge, where the ring has nothing
|
|
5081
|
+
if (inner && sameRounded(outer, inner)) return null;
|
|
5082
|
+
return { outer, inner };
|
|
5083
|
+
}
|
|
5084
|
+
|
|
5085
|
+
/** A position moved out of any of `spans` it is inside, towards their
|
|
5086
|
+
* starts where `towards` is negative and their ends where it is not. */
|
|
5087
|
+
function outOfSpans(
|
|
5088
|
+
at: number,
|
|
5089
|
+
spans: readonly [number, number][],
|
|
5090
|
+
towards: number,
|
|
5091
|
+
): number {
|
|
5092
|
+
for (let moved = true; moved;) {
|
|
5093
|
+
moved = false;
|
|
5094
|
+
for (const [from, to] of spans) {
|
|
5095
|
+
if (at > from && at < to) {
|
|
5096
|
+
at = towards < 0 ? from : to;
|
|
5097
|
+
moved = true;
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5101
|
+
return at;
|
|
5102
|
+
}
|
|
5103
|
+
|
|
5104
|
+
/** Whether two rounded rectangles are one shape. */
|
|
5105
|
+
function sameRounded(a: Rounded, b: Rounded): boolean {
|
|
5106
|
+
return (
|
|
5107
|
+
a.rect.x === b.rect.x &&
|
|
5108
|
+
a.rect.y === b.rect.y &&
|
|
5109
|
+
a.rect.width === b.rect.width &&
|
|
5110
|
+
a.rect.height === b.rect.height &&
|
|
5111
|
+
a.corners.x.every((v, i) => v === b.corners.x[i]) &&
|
|
5112
|
+
a.corners.y.every((v, i) => v === b.corners.y[i])
|
|
5113
|
+
);
|
|
5114
|
+
}
|
|
5115
|
+
|
|
4967
5116
|
/**
|
|
4968
5117
|
* Clip to what a box's border paints, for a layer of `background-clip:
|
|
4969
5118
|
* border-area` (CSS Backgrounds 4, 2.1): each side's width and style, and
|
|
@@ -4971,12 +5120,17 @@ function roundedRing(
|
|
|
4971
5120
|
* the value is for. The shapes are the border painter's own, so that the
|
|
4972
5121
|
* background shows exactly where that border would: the ring `roundedRing`
|
|
4973
5122
|
* fills where a rounded border is solid, the ring the trapezoids of a 3D
|
|
4974
|
-
* style make, and otherwise the rectangles `
|
|
4975
|
-
* time — the dots, the dashes, the two lines of a double border
|
|
5123
|
+
* style make, and otherwise the rectangles `fillSide` fills a side at a
|
|
5124
|
+
* time — the dots, the dashes, the two lines of a double border joined as
|
|
5125
|
+
* they are painted, by the sides' colours though neither is drawn. All of
|
|
4976
5126
|
* them run clockwise and a ring's inside the other way, so the clip is the
|
|
4977
|
-
* union by the non-zero rule, the one every context's `clip` takes.
|
|
4978
|
-
*
|
|
4979
|
-
*
|
|
5127
|
+
* union by the non-zero rule, the one every context's `clip` takes. Each
|
|
5128
|
+
* is placed from the box's edges and then cut to the painted area: placed
|
|
5129
|
+
* from the cut, a border wider than `CLAMP_PAD` put its bands and its
|
|
5130
|
+
* ring's hole a border's width in from wherever the paint was cut, and
|
|
5131
|
+
* the background was painted over the content of a box repainted in part.
|
|
5132
|
+
* False where the border paints nothing in the painted area or the context
|
|
5133
|
+
* cannot clip, and nothing is pushed.
|
|
4980
5134
|
*/
|
|
4981
5135
|
function pushBorderArea(
|
|
4982
5136
|
ctx: PaintContext,
|
|
@@ -4998,43 +5152,60 @@ function pushBorderArea(
|
|
|
4998
5152
|
const w = Math.round(left + box.width) - x;
|
|
4999
5153
|
const h = Math.round(top + frameHeight(box)) - y;
|
|
5000
5154
|
if (w <= 0 || h <= 0) return false;
|
|
5001
|
-
|
|
5002
|
-
if (!area) return false;
|
|
5155
|
+
if (!clampRect(options, x, y, w, h)) return false;
|
|
5003
5156
|
const corners = cornersOf(s, w, h);
|
|
5004
5157
|
ctx.save();
|
|
5005
5158
|
ctx.beginPath();
|
|
5159
|
+
// how many shapes the path has: none, and the border paints nothing here
|
|
5160
|
+
let shapes = 0;
|
|
5006
5161
|
if (corners && canCurve(ctx) && solidBorder(box)) {
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5162
|
+
const ring = cutRing(options, x, y, w, h, corners, t, r, b, l);
|
|
5163
|
+
if (ring) {
|
|
5164
|
+
const add = ({ rect, corners: c }: Rounded, hole: boolean): void =>
|
|
5165
|
+
roundedRect(
|
|
5166
|
+
ctx,
|
|
5167
|
+
rect.x,
|
|
5168
|
+
rect.y,
|
|
5169
|
+
rect.width,
|
|
5170
|
+
rect.height,
|
|
5171
|
+
c,
|
|
5172
|
+
true,
|
|
5173
|
+
hole,
|
|
5174
|
+
);
|
|
5175
|
+
add(ring.outer, false);
|
|
5176
|
+
// the padding edge run the other way, which cuts it out
|
|
5177
|
+
if (ring.inner) add(ring.inner, true);
|
|
5178
|
+
shapes = 1;
|
|
5013
5179
|
}
|
|
5014
|
-
} else if (sculpted(s)) {
|
|
5015
|
-
// a band a side, which the trapezoids cover between them
|
|
5016
|
-
ctx.rect(area.x, area.y, area.w, t);
|
|
5017
|
-
ctx.rect(area.x, area.y + area.h - b, area.w, b);
|
|
5018
|
-
ctx.rect(area.x, area.y + t, l, area.h - t - b);
|
|
5019
|
-
ctx.rect(area.x + area.w - r, area.y + t, r, area.h - t - b);
|
|
5020
5180
|
} else {
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
ew: number,
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
): void => {
|
|
5030
|
-
const rect = clampRect(options, ex, ey, ew, eh);
|
|
5031
|
-
if (rect) fillEdge(path, rect, horizontal ? ex : ey, style, horizontal);
|
|
5181
|
+
// A side at a time, from the box's edges and cut to the painted area
|
|
5182
|
+
// after: a 3D side as a band, which the trapezoids cover between them,
|
|
5183
|
+
// and any other as `fillSide` fills it.
|
|
5184
|
+
const path = {
|
|
5185
|
+
fillRect(ex: number, ey: number, ew: number, eh: number) {
|
|
5186
|
+
ctx.rect!(ex, ey, ew, eh);
|
|
5187
|
+
shapes += 1;
|
|
5188
|
+
},
|
|
5032
5189
|
};
|
|
5033
|
-
const
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5190
|
+
const widths = [t, r, b, l];
|
|
5191
|
+
const joins = doubleJoins(box);
|
|
5192
|
+
const styles: ComputedStyle['borderTopStyle'][] = sculpted(s)
|
|
5193
|
+
? ['solid', 'solid', 'solid', 'solid']
|
|
5194
|
+
: [
|
|
5195
|
+
s.borderTopStyle,
|
|
5196
|
+
s.borderRightStyle,
|
|
5197
|
+
s.borderBottomStyle,
|
|
5198
|
+
s.borderLeftStyle,
|
|
5199
|
+
];
|
|
5200
|
+
for (let at = 0; at < 4; at += 1) {
|
|
5201
|
+
if (widths[at] > 0) {
|
|
5202
|
+
fillSide(path, options, at, x, y, w, h, widths, styles[at], joins);
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5206
|
+
if (!shapes) {
|
|
5207
|
+
ctx.restore();
|
|
5208
|
+
return false;
|
|
5038
5209
|
}
|
|
5039
5210
|
ctx.clip();
|
|
5040
5211
|
return true;
|
|
@@ -5071,10 +5242,12 @@ function sculpted(s: ComputedStyle): boolean {
|
|
|
5071
5242
|
* colour's shadow and `outset` its bottom and right; `groove` is `inset`
|
|
5072
5243
|
* outside `outset`, a band each, and `ridge` the other way round
|
|
5073
5244
|
* (`borderShades`). A side of any other style takes its trapezoid in its
|
|
5074
|
-
* colour. The trapezoids are
|
|
5075
|
-
*
|
|
5076
|
-
*
|
|
5077
|
-
*
|
|
5245
|
+
* colour. The trapezoids are cut to the painted area (`fillPolygon`), which
|
|
5246
|
+
* keeps a long box's far corners out of X's coordinates and a join on its
|
|
5247
|
+
* diagonal: its corners clamped in, a border wider than `CLAMP_PAD` turned
|
|
5248
|
+
* the join to cross the damage at 45° wherever it was, and a scrolled
|
|
5249
|
+
* strip smeared the corners of a thick one. False where the context cannot
|
|
5250
|
+
* draw a path, and the sides are drawn straight.
|
|
5078
5251
|
*/
|
|
5079
5252
|
function paintSculpted(
|
|
5080
5253
|
ctx: PaintContext,
|
|
@@ -5093,10 +5266,6 @@ function paintSculpted(
|
|
|
5093
5266
|
const r = box.borderRight;
|
|
5094
5267
|
const b = box.borderBottom;
|
|
5095
5268
|
const l = box.borderLeft;
|
|
5096
|
-
const cx = (v: number) =>
|
|
5097
|
-
Math.max(area.x - 1, Math.min(v, area.x + area.w + 1));
|
|
5098
|
-
const cy = (v: number) =>
|
|
5099
|
-
Math.max(area.y - 1, Math.min(v, area.y + area.h + 1));
|
|
5100
5269
|
/** The band between the edge `outer` of the way in and `inner`, for one
|
|
5101
5270
|
* side: 0 is the border box, 1 the padding box. */
|
|
5102
5271
|
const band = (side: number, outer: number, inner: number, color: string) => {
|
|
@@ -5117,12 +5286,7 @@ function paintSculpted(
|
|
|
5117
5286
|
? [o.x1, o.y1, o.x0, o.y1, i.x0, i.y1, i.x1, i.y1]
|
|
5118
5287
|
: [o.x0, o.y1, o.x0, o.y0, i.x0, i.y0, i.x0, i.y1];
|
|
5119
5288
|
ctx.fillStyle = color;
|
|
5120
|
-
ctx
|
|
5121
|
-
ctx.moveTo!(cx(points[0]), cy(points[1]));
|
|
5122
|
-
for (let k = 2; k < 8; k += 2)
|
|
5123
|
-
ctx.lineTo!(cx(points[k]), cy(points[k + 1]));
|
|
5124
|
-
ctx.closePath?.();
|
|
5125
|
-
ctx.fill!();
|
|
5289
|
+
fillPolygon(ctx, points, area);
|
|
5126
5290
|
};
|
|
5127
5291
|
const sides: [number, string, string][] = [
|
|
5128
5292
|
[t, s.borderTopStyle, s.borderTopColor],
|
|
@@ -5249,6 +5413,111 @@ function paintCollapsedBorders(
|
|
|
5249
5413
|
}
|
|
5250
5414
|
}
|
|
5251
5415
|
|
|
5416
|
+
/**
|
|
5417
|
+
* One side of a border box at `x, y`, `w` by `h` — `at` counts the top
|
|
5418
|
+
* first and clockwise — as rectangles cut to what the paint reaches: the
|
|
5419
|
+
* top and the bottom the width of the box and the left and the right
|
|
5420
|
+
* between them, so a corner is a square of one side or the other.
|
|
5421
|
+
*
|
|
5422
|
+
* A double side is two lines, a band each along its outer and its inner
|
|
5423
|
+
* edge. Where it meets another double side of its colour (`joins`, the
|
|
5424
|
+
* top left corner first) the lines of the two meet as two frames do, one
|
|
5425
|
+
* inside the other (CSS Backgrounds 3, 4.2 and 4.3): the outer line of the
|
|
5426
|
+
* left or the right runs on to the outer line of the top or the bottom,
|
|
5427
|
+
* and the inner line of the top or the bottom stops at the inner line of
|
|
5428
|
+
* the side. With square corners the outer frame was open at each of them
|
|
5429
|
+
* and the inner line of the top ran on past the side's to the border edge.
|
|
5430
|
+
* Each line is cut to the paint on its own, so a border wider than the
|
|
5431
|
+
* reach around the paint keeps its lines at its edges, and not at the
|
|
5432
|
+
* edges of the part of it cut out.
|
|
5433
|
+
*/
|
|
5434
|
+
function fillSide(
|
|
5435
|
+
ctx: Pick<FillContext, 'fillRect'>,
|
|
5436
|
+
options: PaintOptions,
|
|
5437
|
+
at: number,
|
|
5438
|
+
x: number,
|
|
5439
|
+
y: number,
|
|
5440
|
+
w: number,
|
|
5441
|
+
h: number,
|
|
5442
|
+
widths: readonly number[],
|
|
5443
|
+
style: ComputedStyle['borderTopStyle'],
|
|
5444
|
+
joins: readonly boolean[],
|
|
5445
|
+
): void {
|
|
5446
|
+
const [t, r, b, l] = widths;
|
|
5447
|
+
const width = widths[at];
|
|
5448
|
+
const horizontal = (at & 1) === 0;
|
|
5449
|
+
if (style === 'double' && width >= 3) {
|
|
5450
|
+
const band = doubleBand(width);
|
|
5451
|
+
const line = (lx: number, ly: number, lw: number, lh: number): void => {
|
|
5452
|
+
const rect = clampRect(options, lx, ly, lw, lh);
|
|
5453
|
+
if (rect) ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
|
|
5454
|
+
};
|
|
5455
|
+
if (horizontal) {
|
|
5456
|
+
// the outer line the width of the box, and the inner one from the
|
|
5457
|
+
// inner line of a side it joins, or from the edge of the box
|
|
5458
|
+
const from = joins[at === 0 ? 0 : 3] ? x + l - doubleBand(l) : x;
|
|
5459
|
+
const to = joins[at === 0 ? 1 : 2] ? x + w - r + doubleBand(r) : x + w;
|
|
5460
|
+
line(x, at === 0 ? y : y + h - band, w, band);
|
|
5461
|
+
line(from, at === 0 ? y + t - band : y + h - b, to - from, band);
|
|
5462
|
+
} else {
|
|
5463
|
+
// the outer line from the outer line of a side it joins, or from
|
|
5464
|
+
// that side's inner edge, and the inner one between the two sides
|
|
5465
|
+
const from = joins[at === 3 ? 0 : 1] ? y + doubleBand(t) : y + t;
|
|
5466
|
+
const to = joins[at === 3 ? 3 : 2] ? y + h - doubleBand(b) : y + h - b;
|
|
5467
|
+
line(at === 3 ? x : x + w - band, from, band, to - from);
|
|
5468
|
+
line(at === 3 ? x + l - band : x + w - r, y + t, band, h - t - b);
|
|
5469
|
+
}
|
|
5470
|
+
return;
|
|
5471
|
+
}
|
|
5472
|
+
const ex = at === 1 ? x + w - r : x;
|
|
5473
|
+
const ey = at === 0 ? y : at === 2 ? y + h - b : y + t;
|
|
5474
|
+
const rect = horizontal
|
|
5475
|
+
? clampRect(options, ex, ey, w, width)
|
|
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);
|
|
5480
|
+
}
|
|
5481
|
+
|
|
5482
|
+
/**
|
|
5483
|
+
* The corners of a box where two double sides of one colour meet, the top
|
|
5484
|
+
* left first and clockwise, and whose lines `fillSide` joins. By the
|
|
5485
|
+
* colours as written, a transparent one included, which is how a clip to
|
|
5486
|
+
* the border's area draws the border it does not paint.
|
|
5487
|
+
*/
|
|
5488
|
+
function doubleJoins(box: Frame): readonly boolean[] {
|
|
5489
|
+
const s = box.style;
|
|
5490
|
+
const color = (
|
|
5491
|
+
width: number,
|
|
5492
|
+
style: ComputedStyle['borderTopStyle'],
|
|
5493
|
+
value: string,
|
|
5494
|
+
): string | null =>
|
|
5495
|
+
style === 'double' && width >= 3 ? inkColor(value, s.color) : null;
|
|
5496
|
+
const top = color(box.borderTop, s.borderTopStyle, s.borderTopColor);
|
|
5497
|
+
const bottom = color(
|
|
5498
|
+
box.borderBottom,
|
|
5499
|
+
s.borderBottomStyle,
|
|
5500
|
+
s.borderBottomColor,
|
|
5501
|
+
);
|
|
5502
|
+
if (top === null && bottom === null) return UNJOINED;
|
|
5503
|
+
const right = color(box.borderRight, s.borderRightStyle, s.borderRightColor);
|
|
5504
|
+
const left = color(box.borderLeft, s.borderLeftStyle, s.borderLeftColor);
|
|
5505
|
+
return [
|
|
5506
|
+
top !== null && top === left,
|
|
5507
|
+
top !== null && top === right,
|
|
5508
|
+
bottom !== null && bottom === right,
|
|
5509
|
+
bottom !== null && bottom === left,
|
|
5510
|
+
];
|
|
5511
|
+
}
|
|
5512
|
+
|
|
5513
|
+
const UNJOINED: readonly boolean[] = [false, false, false, false];
|
|
5514
|
+
|
|
5515
|
+
/** How wide each of a double border's two lines is, the gap between them
|
|
5516
|
+
* what is left of the side. */
|
|
5517
|
+
function doubleBand(thickness: number): number {
|
|
5518
|
+
return Math.max(1, Math.floor(thickness / 3));
|
|
5519
|
+
}
|
|
5520
|
+
|
|
5252
5521
|
function fillEdge(
|
|
5253
5522
|
ctx: Pick<FillContext, 'fillRect'>,
|
|
5254
5523
|
rect: { x: number; y: number; w: number; h: number },
|
|
@@ -5277,7 +5546,7 @@ function fillEdge(
|
|
|
5277
5546
|
return;
|
|
5278
5547
|
}
|
|
5279
5548
|
if (style === 'double' && thickness >= 3) {
|
|
5280
|
-
const band =
|
|
5549
|
+
const band = doubleBand(thickness);
|
|
5281
5550
|
if (horizontal) {
|
|
5282
5551
|
ctx.fillRect(x, y, length, band);
|
|
5283
5552
|
ctx.fillRect(x, y + thickness - band, length, band);
|
|
@@ -5295,6 +5564,8 @@ function paintMarker(
|
|
|
5295
5564
|
ctx: PaintContext,
|
|
5296
5565
|
marker: Marker,
|
|
5297
5566
|
options: PaintOptions,
|
|
5567
|
+
/** The item's colour scheme, which an SVG image in it is drawn in. */
|
|
5568
|
+
scheme: 'light' | 'dark',
|
|
5298
5569
|
): void {
|
|
5299
5570
|
const x = marker.x + options.originX;
|
|
5300
5571
|
const y = marker.y + options.originY;
|
|
@@ -5304,7 +5575,15 @@ function paintMarker(
|
|
|
5304
5575
|
const loaded = options.backgroundImageFor?.(url);
|
|
5305
5576
|
if (!loaded || !(width > 0 && height > 0)) return;
|
|
5306
5577
|
if (loaded.image instanceof SvgDrawing) {
|
|
5307
|
-
loaded.image.
|
|
5578
|
+
loaded.image.drawImage(
|
|
5579
|
+
ctx,
|
|
5580
|
+
x,
|
|
5581
|
+
y,
|
|
5582
|
+
width,
|
|
5583
|
+
height,
|
|
5584
|
+
options.scale ?? 1,
|
|
5585
|
+
scheme,
|
|
5586
|
+
);
|
|
5308
5587
|
} else ctx.drawImage?.(loaded.image, x, y, width, height);
|
|
5309
5588
|
return;
|
|
5310
5589
|
}
|
|
@@ -5336,7 +5615,15 @@ function paintImage(ctx: PaintContext, box: Box, options: PaintOptions): void {
|
|
|
5336
5615
|
const clipped =
|
|
5337
5616
|
(!!corners || past) && pushClip(ctx, { x, y, w, h }, corners);
|
|
5338
5617
|
if (image instanceof SvgDrawing) {
|
|
5339
|
-
image.
|
|
5618
|
+
image.drawImage(
|
|
5619
|
+
ctx,
|
|
5620
|
+
at.x,
|
|
5621
|
+
at.y,
|
|
5622
|
+
at.w,
|
|
5623
|
+
at.h,
|
|
5624
|
+
options.scale ?? 1,
|
|
5625
|
+
box.style.colorScheme,
|
|
5626
|
+
);
|
|
5340
5627
|
} else ctx.drawImage!(image, at.x, at.y, at.w, at.h);
|
|
5341
5628
|
if (clipped) ctx.restore();
|
|
5342
5629
|
return;
|
|
@@ -5427,7 +5714,8 @@ function fitted(
|
|
|
5427
5714
|
/** An inline `<svg>`, drawn in its content box. Its `currentColor` is the
|
|
5428
5715
|
* box's `color`, as an icon's is the text's around it, and what it is
|
|
5429
5716
|
* painted with the box's `fill` and `stroke`, where the document's style
|
|
5430
|
-
* sheets set them: an icon set's `.icon { fill: currentColor }`.
|
|
5717
|
+
* sheets set them: an icon set's `.icon { fill: currentColor }`. What
|
|
5718
|
+
* they give a shape inside it is asked for here (`BoxTree.shapeStyler`). */
|
|
5431
5719
|
function paintSvg(ctx: PaintContext, box: Box, options: PaintOptions): void {
|
|
5432
5720
|
if (!box.el) return;
|
|
5433
5721
|
const x = Math.round(box.contentX + options.originX);
|
|
@@ -5449,6 +5737,7 @@ function paintSvg(ctx: PaintContext, box: Box, options: PaintOptions): void {
|
|
|
5449
5737
|
style.color,
|
|
5450
5738
|
paint(style.fill),
|
|
5451
5739
|
paint(style.stroke),
|
|
5740
|
+
options.shapeStyler?.(box) ?? null,
|
|
5452
5741
|
);
|
|
5453
5742
|
}
|
|
5454
5743
|
|