@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.
Files changed (51) hide show
  1. package/dist/html/css/cascade.d.ts +42 -1
  2. package/dist/html/css/cascade.d.ts.map +1 -1
  3. package/dist/html/css/cascade.js +310 -12
  4. package/dist/html/css/cascade.js.map +1 -1
  5. package/dist/html/css/shapes.d.ts +44 -0
  6. package/dist/html/css/shapes.d.ts.map +1 -0
  7. package/dist/html/css/shapes.js +194 -0
  8. package/dist/html/css/shapes.js.map +1 -0
  9. package/dist/html/css/style.d.ts.map +1 -1
  10. package/dist/html/css/style.js +1 -15
  11. package/dist/html/css/style.js.map +1 -1
  12. package/dist/html/layout/block.d.ts +16 -5
  13. package/dist/html/layout/block.d.ts.map +1 -1
  14. package/dist/html/layout/block.js +26 -5
  15. package/dist/html/layout/block.js.map +1 -1
  16. package/dist/html/layout/boxes.d.ts +13 -0
  17. package/dist/html/layout/boxes.d.ts.map +1 -1
  18. package/dist/html/layout/boxes.js +18 -0
  19. package/dist/html/layout/boxes.js.map +1 -1
  20. package/dist/html/layout/css-grid.d.ts.map +1 -1
  21. package/dist/html/layout/css-grid.js +58 -10
  22. package/dist/html/layout/css-grid.js.map +1 -1
  23. package/dist/html/layout/flex.js +19 -6
  24. package/dist/html/layout/flex.js.map +1 -1
  25. package/dist/html/layout/table.d.ts.map +1 -1
  26. package/dist/html/layout/table.js +38 -16
  27. package/dist/html/layout/table.js.map +1 -1
  28. package/dist/html/node.d.ts +1 -0
  29. package/dist/html/node.d.ts.map +1 -1
  30. package/dist/html/node.js +57 -12
  31. package/dist/html/node.js.map +1 -1
  32. package/dist/html/paint.d.ts +4 -1
  33. package/dist/html/paint.d.ts.map +1 -1
  34. package/dist/html/paint.js +269 -84
  35. package/dist/html/paint.js.map +1 -1
  36. package/dist/html/svg.d.ts +27 -10
  37. package/dist/html/svg.d.ts.map +1 -1
  38. package/dist/html/svg.js +259 -46
  39. package/dist/html/svg.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/html/css/cascade.ts +343 -8
  42. package/src/html/css/shapes.ts +239 -0
  43. package/src/html/css/style.ts +1 -13
  44. package/src/html/layout/block.ts +27 -5
  45. package/src/html/layout/boxes.ts +30 -0
  46. package/src/html/layout/css-grid.ts +77 -13
  47. package/src/html/layout/flex.ts +22 -9
  48. package/src/html/layout/table.ts +45 -18
  49. package/src/html/node.ts +54 -11
  50. package/src/html/paint.ts +379 -90
  51. package/src/html/svg.ts +318 -39
@@ -494,6 +494,7 @@ export function paintDocument(ctx, tree, options) {
494
494
  canvasSource: canvas?.source,
495
495
  negative: tree.negative,
496
496
  selectionStyler: options.selection ? tree.selectionStyler : null,
497
+ shapeStyler: tree.shapeStyler,
497
498
  });
498
499
  ctx.restore();
499
500
  }
@@ -1175,8 +1176,9 @@ function paintContent(ctx, box, options) {
1175
1176
  paintStacked(ctx, child, options);
1176
1177
  }
1177
1178
  if (visible) {
1178
- if (box.marker)
1179
- paintMarker(ctx, box.marker, options);
1179
+ if (box.marker) {
1180
+ paintMarker(ctx, box.marker, options, box.style.colorScheme);
1181
+ }
1180
1182
  if (box.replaced === 'image')
1181
1183
  paintImage(ctx, box, options);
1182
1184
  else if (box.replaced === 'svg')
@@ -1742,7 +1744,7 @@ function paintFlowLines(ctx, box, options, outlines) {
1742
1744
  if (!intersects(child, options))
1743
1745
  continue;
1744
1746
  if (child.marker && child.style.visibility === 'visible') {
1745
- paintMarker(ctx, child.marker, options);
1747
+ paintMarker(ctx, child.marker, options, child.style.colorScheme);
1746
1748
  }
1747
1749
  if (child.lines)
1748
1750
  paintLines(ctx, child, options);
@@ -3426,12 +3428,13 @@ function paintBackgroundImage(ctx, style, area, at, options, corners = null) {
3426
3428
  // a drawing is drawn a tile at a time, at the size it was given
3427
3429
  if (tiles <= MAX_TILES) {
3428
3430
  for (let y = fromY; y < toY; y += stepY) {
3429
- for (let x = fromX; x < toX; x += stepX)
3430
- svg.draw(ctx, Math.round(x), Math.round(y), iw, ih, scale);
3431
+ for (let x = fromX; x < toX; x += stepX) {
3432
+ svg.drawImage(ctx, Math.round(x), Math.round(y), iw, ih, scale, style.colorScheme);
3433
+ }
3431
3434
  }
3432
3435
  }
3433
3436
  else {
3434
- svg.draw(ctx, x0, y0, iw, ih, scale);
3437
+ svg.drawImage(ctx, x0, y0, iw, ih, scale, style.colorScheme);
3435
3438
  }
3436
3439
  }
3437
3440
  else if (tiles > 1 &&
@@ -3647,7 +3650,8 @@ function paintOutline(ctx, box, options) {
3647
3650
  * width: the CSS triangle a dropdown's caret and a tooltip's arrow are
3648
3651
  * drawn with, which came out as the rectangle around it. Only a solid side
3649
3652
  * is cut, and only against a side that is solid or paints nothing; dots,
3650
- * dashes and a double border's two lines keep their rectangles.
3653
+ * dashes and a double border's two lines keep their rectangles, the lines
3654
+ * joined where two double sides of one colour meet (`fillSide`).
3651
3655
  */
3652
3656
  function paintBorders(ctx, box, options) {
3653
3657
  // most boxes have none, and are asked on every paint
@@ -3687,14 +3691,12 @@ function paintBorders(ctx, box, options) {
3687
3691
  const tr = paths && mitred(t, r, topInk, rightInk, topStyle, rightStyle);
3688
3692
  const br = paths && mitred(b, r, bottomInk, rightInk, bottomStyle, rightStyle);
3689
3693
  const bl = paths && mitred(b, l, bottomInk, leftInk, bottomStyle, leftStyle);
3690
- const edge = (ex, ey, ew, eh, style, color, horizontal) => {
3691
- const rect = clampRect(options, ex, ey, ew, eh);
3692
- if (!rect)
3693
- return;
3694
+ const widths = [t, r, b, l];
3695
+ const joins = doubleJoins(box);
3696
+ /** A side with square corners, the top first and clockwise. */
3697
+ const edge = (at, style, color) => {
3694
3698
  ctx.fillStyle = color;
3695
- // The un-clamped start is the dash phase's origin, so the pattern does
3696
- // not crawl as the viewport moves along a long edge.
3697
- fillEdge(ctx, rect, horizontal ? ex : ey, style, horizontal);
3699
+ fillSide(ctx, options, at, x, y, w, h, widths, style, joins);
3698
3700
  };
3699
3701
  /** A side with a cut corner: its outer edge and then its inner one. */
3700
3702
  const side = (points, color) => {
@@ -3724,7 +3726,7 @@ function paintBorders(ctx, box, options) {
3724
3726
  side([x, y, x1, y, cutRight ? px1 : x1, py0, cutLeft ? px0 : x, py0], topInk);
3725
3727
  }
3726
3728
  else
3727
- edge(x, y, w, t, topStyle, topInk, true);
3729
+ edge(0, topStyle, topInk);
3728
3730
  }
3729
3731
  if (bottomInk !== null) {
3730
3732
  const cutLeft = bl && !over(leftInk);
@@ -3733,21 +3735,21 @@ function paintBorders(ctx, box, options) {
3733
3735
  side([x1, y1, x, y1, cutLeft ? px0 : x, py1, cutRight ? px1 : x1, py1], bottomInk);
3734
3736
  }
3735
3737
  else
3736
- edge(x, y1 - b, w, b, bottomStyle, bottomInk, true);
3738
+ edge(2, bottomStyle, bottomInk);
3737
3739
  }
3738
3740
  if (leftInk !== null) {
3739
3741
  if (tl || bl) {
3740
3742
  side([x, bl ? y1 : py1, x, tl ? y : py0, px0, py0, px0, py1], leftInk);
3741
3743
  }
3742
3744
  else
3743
- edge(x, y + t, l, h - t - b, leftStyle, leftInk, false);
3745
+ edge(3, leftStyle, leftInk);
3744
3746
  }
3745
3747
  if (rightInk !== null) {
3746
3748
  if (tr || br) {
3747
3749
  side([x1, tr ? y : py0, x1, br ? y1 : py1, px1, py1, px1, py0], rightInk);
3748
3750
  }
3749
3751
  else
3750
- edge(x1 - r, y + t, r, h - t - b, rightStyle, rightInk, false);
3752
+ edge(1, rightStyle, rightInk);
3751
3753
  }
3752
3754
  }
3753
3755
  /**
@@ -3858,9 +3860,10 @@ function paintBorderImage(ctx, box, options) {
3858
3860
  const svg = image instanceof SvgDrawing ? image : null;
3859
3861
  const w = Math.max(1, Math.round(cw));
3860
3862
  const h = Math.max(1, Math.round(ch));
3861
- const drawn = options.cached?.(`border-image|${key}|${w}x${h}`, w, h, (sctx) => {
3863
+ const scheme = box.style.colorScheme;
3864
+ const drawn = options.cached?.(`border-image|${key}|${w}x${h}|${scheme}`, w, h, (sctx) => {
3862
3865
  if (svg)
3863
- svg.draw(sctx, 0, 0, w, h, scale);
3866
+ svg.drawImage(sctx, 0, 0, w, h, scale, scheme);
3864
3867
  else if (typeof source !== 'string') {
3865
3868
  const paint = gradientFill(sctx, source, 0, 0, w, h, box.style.color);
3866
3869
  if (paint)
@@ -4062,22 +4065,107 @@ function roundedRing(ctx, box, x, y, w, h, options) {
4062
4065
  return true;
4063
4066
  if (isTransparent(color))
4064
4067
  return true;
4065
- const rect = clampRect(options, x, y, w, h);
4066
- if (!rect)
4068
+ const ring = cutRing(options, x, y, w, h, corners, box.borderTop, box.borderRight, box.borderBottom, box.borderLeft);
4069
+ if (!ring)
4067
4070
  return true;
4068
- const top = box.borderTop;
4069
- const right = box.borderRight;
4070
- const bottom = box.borderBottom;
4071
- const left = box.borderLeft;
4071
+ const { outer, inner } = ring;
4072
4072
  ctx.fillStyle = inkColor(color, s.color);
4073
- fillRing(ctx, { x: rect.x, y: rect.y, width: rect.w, height: rect.h }, corners, {
4074
- x: rect.x + left,
4075
- y: rect.y + top,
4076
- width: Math.max(0, rect.w - left - right),
4077
- height: Math.max(0, rect.h - top - bottom),
4078
- }, insetCorners(corners, top, right, bottom, left));
4073
+ fillRing(ctx, outer.rect, outer.corners, inner ? inner.rect : { x: 0, y: 0, width: 0, height: 0 }, inner ? inner.corners : outer.corners);
4079
4074
  return true;
4080
4075
  }
4076
+ /**
4077
+ * A rounded box's ring — its border edge and its padding edge, the box
4078
+ * inside its borders — cut to the neighbourhood of what is being painted,
4079
+ * as `clampRect` cuts a rectangle. Both edges are the box's own, cut to
4080
+ * one window, so what is left is the part of the true ring in it. Cutting
4081
+ * the box first and insetting the cut by the borders put the hole a
4082
+ * border's width in from the cut rather than from the box's edge, and left
4083
+ * none where a border was wider than `CLAMP_PAD`: the ring filled whatever
4084
+ * of the box a paint reached. A cut is straight, so a side of the window
4085
+ * is moved out past any corner's curve it would cross, and a corner on a
4086
+ * side the window cut is square, its curve being outside. Null where the
4087
+ * ring has nothing in the window.
4088
+ */
4089
+ function cutRing(options, x, y, w, h, corners, top, right, bottom, left) {
4090
+ const { x: cx, y: cy } = corners;
4091
+ // where each corner's curve runs, across and down — the padding edge's
4092
+ // run inside the border edge's
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);
4134
+ if (!outer)
4135
+ return null;
4136
+ const iw = w - left - right;
4137
+ const ih = h - top - bottom;
4138
+ const inner = iw > 0 && ih > 0
4139
+ ? cut(x + left, y + top, iw, ih, insetCorners(corners, top, right, bottom, left))
4140
+ : null;
4141
+ // a window inside the padding edge, where the ring has nothing
4142
+ if (inner && sameRounded(outer, inner))
4143
+ return null;
4144
+ return { outer, inner };
4145
+ }
4146
+ /** A position moved out of any of `spans` it is inside, towards their
4147
+ * starts where `towards` is negative and their ends where it is not. */
4148
+ function outOfSpans(at, spans, towards) {
4149
+ for (let moved = true; moved;) {
4150
+ moved = false;
4151
+ for (const [from, to] of spans) {
4152
+ if (at > from && at < to) {
4153
+ at = towards < 0 ? from : to;
4154
+ moved = true;
4155
+ }
4156
+ }
4157
+ }
4158
+ return at;
4159
+ }
4160
+ /** Whether two rounded rectangles are one shape. */
4161
+ function sameRounded(a, b) {
4162
+ return (a.rect.x === b.rect.x &&
4163
+ a.rect.y === b.rect.y &&
4164
+ a.rect.width === b.rect.width &&
4165
+ a.rect.height === b.rect.height &&
4166
+ a.corners.x.every((v, i) => v === b.corners.x[i]) &&
4167
+ a.corners.y.every((v, i) => v === b.corners.y[i]));
4168
+ }
4081
4169
  /**
4082
4170
  * Clip to what a box's border paints, for a layer of `background-clip:
4083
4171
  * border-area` (CSS Backgrounds 4, 2.1): each side's width and style, and
@@ -4085,12 +4173,17 @@ function roundedRing(ctx, box, x, y, w, h, options) {
4085
4173
  * the value is for. The shapes are the border painter's own, so that the
4086
4174
  * background shows exactly where that border would: the ring `roundedRing`
4087
4175
  * fills where a rounded border is solid, the ring the trapezoids of a 3D
4088
- * style make, and otherwise the rectangles `fillEdge` fills a side at a
4089
- * time — the dots, the dashes, the two lines of a double border. All of
4176
+ * style make, and otherwise the rectangles `fillSide` fills a side at a
4177
+ * time — the dots, the dashes, the two lines of a double border joined as
4178
+ * they are painted, by the sides' colours though neither is drawn. All of
4090
4179
  * them run clockwise and a ring's inside the other way, so the clip is the
4091
- * union by the non-zero rule, the one every context's `clip` takes. False
4092
- * where the border paints nothing or the context cannot clip, and nothing
4093
- * is pushed.
4180
+ * union by the non-zero rule, the one every context's `clip` takes. Each
4181
+ * is placed from the box's edges and then cut to the painted area: placed
4182
+ * from the cut, a border wider than `CLAMP_PAD` put its bands and its
4183
+ * ring's hole a border's width in from wherever the paint was cut, and
4184
+ * the background was painted over the content of a box repainted in part.
4185
+ * False where the border paints nothing in the painted area or the context
4186
+ * cannot clip, and nothing is pushed.
4094
4187
  */
4095
4188
  function pushBorderArea(ctx, box, options) {
4096
4189
  const t = box.borderTop;
@@ -4109,44 +4202,53 @@ function pushBorderArea(ctx, box, options) {
4109
4202
  const h = Math.round(top + frameHeight(box)) - y;
4110
4203
  if (w <= 0 || h <= 0)
4111
4204
  return false;
4112
- const area = clampRect(options, x, y, w, h);
4113
- if (!area)
4205
+ if (!clampRect(options, x, y, w, h))
4114
4206
  return false;
4115
4207
  const corners = cornersOf(s, w, h);
4116
4208
  ctx.save();
4117
4209
  ctx.beginPath();
4210
+ // how many shapes the path has: none, and the border paints nothing here
4211
+ let shapes = 0;
4118
4212
  if (corners && canCurve(ctx) && solidBorder(box)) {
4119
- roundedRect(ctx, area.x, area.y, area.w, area.h, corners, true);
4120
- const iw = area.w - l - r;
4121
- const ih = area.h - t - b;
4122
- if (iw > 0 && ih > 0) {
4123
- const inner = insetCorners(corners, t, r, b, l);
4124
- roundedRect(ctx, area.x + l, area.y + t, iw, ih, inner, true, true);
4213
+ const ring = cutRing(options, x, y, w, h, corners, t, r, b, l);
4214
+ if (ring) {
4215
+ const add = ({ rect, corners: c }, hole) => roundedRect(ctx, rect.x, rect.y, rect.width, rect.height, c, true, hole);
4216
+ add(ring.outer, false);
4217
+ // the padding edge run the other way, which cuts it out
4218
+ if (ring.inner)
4219
+ add(ring.inner, true);
4220
+ shapes = 1;
4125
4221
  }
4126
4222
  }
4127
- else if (sculpted(s)) {
4128
- // a band a side, which the trapezoids cover between them
4129
- ctx.rect(area.x, area.y, area.w, t);
4130
- ctx.rect(area.x, area.y + area.h - b, area.w, b);
4131
- ctx.rect(area.x, area.y + t, l, area.h - t - b);
4132
- ctx.rect(area.x + area.w - r, area.y + t, r, area.h - t - b);
4133
- }
4134
4223
  else {
4135
- const path = { fillRect: ctx.rect.bind(ctx) };
4136
- const edge = (ex, ey, ew, eh, style, horizontal) => {
4137
- const rect = clampRect(options, ex, ey, ew, eh);
4138
- if (rect)
4139
- fillEdge(path, rect, horizontal ? ex : ey, style, horizontal);
4224
+ // A side at a time, from the box's edges and cut to the painted area
4225
+ // after: a 3D side as a band, which the trapezoids cover between them,
4226
+ // and any other as `fillSide` fills it.
4227
+ const path = {
4228
+ fillRect(ex, ey, ew, eh) {
4229
+ ctx.rect(ex, ey, ew, eh);
4230
+ shapes += 1;
4231
+ },
4140
4232
  };
4141
- const between = h - t - b;
4142
- if (t > 0)
4143
- edge(x, y, w, t, s.borderTopStyle, true);
4144
- if (b > 0)
4145
- edge(x, y + h - b, w, b, s.borderBottomStyle, true);
4146
- if (l > 0)
4147
- edge(x, y + t, l, between, s.borderLeftStyle, false);
4148
- if (r > 0)
4149
- edge(x + w - r, y + t, r, between, s.borderRightStyle, false);
4233
+ const widths = [t, r, b, l];
4234
+ const joins = doubleJoins(box);
4235
+ const styles = sculpted(s)
4236
+ ? ['solid', 'solid', 'solid', 'solid']
4237
+ : [
4238
+ s.borderTopStyle,
4239
+ s.borderRightStyle,
4240
+ s.borderBottomStyle,
4241
+ s.borderLeftStyle,
4242
+ ];
4243
+ for (let at = 0; at < 4; at += 1) {
4244
+ if (widths[at] > 0) {
4245
+ fillSide(path, options, at, x, y, w, h, widths, styles[at], joins);
4246
+ }
4247
+ }
4248
+ }
4249
+ if (!shapes) {
4250
+ ctx.restore();
4251
+ return false;
4150
4252
  }
4151
4253
  ctx.clip();
4152
4254
  return true;
@@ -4175,10 +4277,12 @@ function sculpted(s) {
4175
4277
  * colour's shadow and `outset` its bottom and right; `groove` is `inset`
4176
4278
  * outside `outset`, a band each, and `ridge` the other way round
4177
4279
  * (`borderShades`). A side of any other style takes its trapezoid in its
4178
- * colour. The trapezoids are clamped to the painted area, which leaves a
4179
- * cut end square and out of sight and keeps a long box's far corners out
4180
- * of X's coordinates. False where the context cannot draw a path, and the
4181
- * sides are drawn straight.
4280
+ * colour. The trapezoids are cut to the painted area (`fillPolygon`), which
4281
+ * keeps a long box's far corners out of X's coordinates and a join on its
4282
+ * diagonal: its corners clamped in, a border wider than `CLAMP_PAD` turned
4283
+ * the join to cross the damage at 45° wherever it was, and a scrolled
4284
+ * strip smeared the corners of a thick one. False where the context cannot
4285
+ * draw a path, and the sides are drawn straight.
4182
4286
  */
4183
4287
  function paintSculpted(ctx, box, x, y, w, h, options) {
4184
4288
  if (!ctx.beginPath || !ctx.moveTo || !ctx.lineTo || !ctx.fill)
@@ -4191,8 +4295,6 @@ function paintSculpted(ctx, box, x, y, w, h, options) {
4191
4295
  const r = box.borderRight;
4192
4296
  const b = box.borderBottom;
4193
4297
  const l = box.borderLeft;
4194
- const cx = (v) => Math.max(area.x - 1, Math.min(v, area.x + area.w + 1));
4195
- const cy = (v) => Math.max(area.y - 1, Math.min(v, area.y + area.h + 1));
4196
4298
  /** The band between the edge `outer` of the way in and `inner`, for one
4197
4299
  * side: 0 is the border box, 1 the padding box. */
4198
4300
  const band = (side, outer, inner, color) => {
@@ -4212,12 +4314,7 @@ function paintSculpted(ctx, box, x, y, w, h, options) {
4212
4314
  ? [o.x1, o.y1, o.x0, o.y1, i.x0, i.y1, i.x1, i.y1]
4213
4315
  : [o.x0, o.y1, o.x0, o.y0, i.x0, i.y0, i.x0, i.y1];
4214
4316
  ctx.fillStyle = color;
4215
- ctx.beginPath();
4216
- ctx.moveTo(cx(points[0]), cy(points[1]));
4217
- for (let k = 2; k < 8; k += 2)
4218
- ctx.lineTo(cx(points[k]), cy(points[k + 1]));
4219
- ctx.closePath?.();
4220
- ctx.fill();
4317
+ fillPolygon(ctx, points, area);
4221
4318
  };
4222
4319
  const sides = [
4223
4320
  [t, s.borderTopStyle, s.borderTopColor],
@@ -4329,6 +4426,91 @@ function paintCollapsedBorders(ctx, table, options) {
4329
4426
  fillEdge(ctx, rect, s.horizontal ? s.x : s.y, s.border.style, s.horizontal);
4330
4427
  }
4331
4428
  }
4429
+ /**
4430
+ * One side of a border box at `x, y`, `w` by `h` — `at` counts the top
4431
+ * first and clockwise — as rectangles cut to what the paint reaches: the
4432
+ * top and the bottom the width of the box and the left and the right
4433
+ * between them, so a corner is a square of one side or the other.
4434
+ *
4435
+ * A double side is two lines, a band each along its outer and its inner
4436
+ * edge. Where it meets another double side of its colour (`joins`, the
4437
+ * top left corner first) the lines of the two meet as two frames do, one
4438
+ * inside the other (CSS Backgrounds 3, 4.2 and 4.3): the outer line of the
4439
+ * left or the right runs on to the outer line of the top or the bottom,
4440
+ * and the inner line of the top or the bottom stops at the inner line of
4441
+ * the side. With square corners the outer frame was open at each of them
4442
+ * and the inner line of the top ran on past the side's to the border edge.
4443
+ * Each line is cut to the paint on its own, so a border wider than the
4444
+ * reach around the paint keeps its lines at its edges, and not at the
4445
+ * edges of the part of it cut out.
4446
+ */
4447
+ function fillSide(ctx, options, at, x, y, w, h, widths, style, joins) {
4448
+ const [t, r, b, l] = widths;
4449
+ const width = widths[at];
4450
+ const horizontal = (at & 1) === 0;
4451
+ if (style === 'double' && width >= 3) {
4452
+ const band = doubleBand(width);
4453
+ const line = (lx, ly, lw, lh) => {
4454
+ const rect = clampRect(options, lx, ly, lw, lh);
4455
+ if (rect)
4456
+ ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
4457
+ };
4458
+ if (horizontal) {
4459
+ // the outer line the width of the box, and the inner one from the
4460
+ // inner line of a side it joins, or from the edge of the box
4461
+ const from = joins[at === 0 ? 0 : 3] ? x + l - doubleBand(l) : x;
4462
+ const to = joins[at === 0 ? 1 : 2] ? x + w - r + doubleBand(r) : x + w;
4463
+ line(x, at === 0 ? y : y + h - band, w, band);
4464
+ line(from, at === 0 ? y + t - band : y + h - b, to - from, band);
4465
+ }
4466
+ else {
4467
+ // the outer line from the outer line of a side it joins, or from
4468
+ // that side's inner edge, and the inner one between the two sides
4469
+ const from = joins[at === 3 ? 0 : 1] ? y + doubleBand(t) : y + t;
4470
+ const to = joins[at === 3 ? 3 : 2] ? y + h - doubleBand(b) : y + h - b;
4471
+ line(at === 3 ? x : x + w - band, from, band, to - from);
4472
+ line(at === 3 ? x + l - band : x + w - r, y + t, band, h - t - b);
4473
+ }
4474
+ return;
4475
+ }
4476
+ const ex = at === 1 ? x + w - r : x;
4477
+ const ey = at === 0 ? y : at === 2 ? y + h - b : y + t;
4478
+ const rect = horizontal
4479
+ ? clampRect(options, ex, ey, w, width)
4480
+ : clampRect(options, ex, ey, width, h - t - b);
4481
+ // The un-clamped start is the dash phase's origin, so the pattern does
4482
+ // not crawl as the viewport moves along a long edge.
4483
+ if (rect)
4484
+ fillEdge(ctx, rect, horizontal ? ex : ey, style, horizontal);
4485
+ }
4486
+ /**
4487
+ * The corners of a box where two double sides of one colour meet, the top
4488
+ * left first and clockwise, and whose lines `fillSide` joins. By the
4489
+ * colours as written, a transparent one included, which is how a clip to
4490
+ * the border's area draws the border it does not paint.
4491
+ */
4492
+ function doubleJoins(box) {
4493
+ const s = box.style;
4494
+ const color = (width, style, value) => style === 'double' && width >= 3 ? inkColor(value, s.color) : null;
4495
+ const top = color(box.borderTop, s.borderTopStyle, s.borderTopColor);
4496
+ const bottom = color(box.borderBottom, s.borderBottomStyle, s.borderBottomColor);
4497
+ if (top === null && bottom === null)
4498
+ return UNJOINED;
4499
+ const right = color(box.borderRight, s.borderRightStyle, s.borderRightColor);
4500
+ const left = color(box.borderLeft, s.borderLeftStyle, s.borderLeftColor);
4501
+ return [
4502
+ top !== null && top === left,
4503
+ top !== null && top === right,
4504
+ bottom !== null && bottom === right,
4505
+ bottom !== null && bottom === left,
4506
+ ];
4507
+ }
4508
+ const UNJOINED = [false, false, false, false];
4509
+ /** How wide each of a double border's two lines is, the gap between them
4510
+ * what is left of the side. */
4511
+ function doubleBand(thickness) {
4512
+ return Math.max(1, Math.floor(thickness / 3));
4513
+ }
4332
4514
  function fillEdge(ctx, rect, phaseOrigin, style, horizontal) {
4333
4515
  const { x, y, w, h } = rect;
4334
4516
  const length = horizontal ? w : h;
@@ -4355,7 +4537,7 @@ function fillEdge(ctx, rect, phaseOrigin, style, horizontal) {
4355
4537
  return;
4356
4538
  }
4357
4539
  if (style === 'double' && thickness >= 3) {
4358
- const band = Math.max(1, Math.floor(thickness / 3));
4540
+ const band = doubleBand(thickness);
4359
4541
  if (horizontal) {
4360
4542
  ctx.fillRect(x, y, length, band);
4361
4543
  ctx.fillRect(x, y + thickness - band, length, band);
@@ -4369,7 +4551,9 @@ function fillEdge(ctx, rect, phaseOrigin, style, horizontal) {
4369
4551
  ctx.fillRect(x, y, w, h);
4370
4552
  }
4371
4553
  /** A list item's bullet or number, in the margin. */
4372
- function paintMarker(ctx, marker, options) {
4554
+ function paintMarker(ctx, marker, options,
4555
+ /** The item's colour scheme, which an SVG image in it is drawn in. */
4556
+ scheme) {
4373
4557
  const x = marker.x + options.originX;
4374
4558
  const y = marker.y + options.originY;
4375
4559
  if (marker.image) {
@@ -4379,7 +4563,7 @@ function paintMarker(ctx, marker, options) {
4379
4563
  if (!loaded || !(width > 0 && height > 0))
4380
4564
  return;
4381
4565
  if (loaded.image instanceof SvgDrawing) {
4382
- loaded.image.draw(ctx, x, y, width, height, options.scale ?? 1);
4566
+ loaded.image.drawImage(ctx, x, y, width, height, options.scale ?? 1, scheme);
4383
4567
  }
4384
4568
  else
4385
4569
  ctx.drawImage?.(loaded.image, x, y, width, height);
@@ -4411,7 +4595,7 @@ function paintImage(ctx, box, options) {
4411
4595
  const past = at.x < x || at.y < y || at.x + at.w > x + w || at.y + at.h > y + h;
4412
4596
  const clipped = (!!corners || past) && pushClip(ctx, { x, y, w, h }, corners);
4413
4597
  if (image instanceof SvgDrawing) {
4414
- image.draw(ctx, at.x, at.y, at.w, at.h, options.scale ?? 1);
4598
+ image.drawImage(ctx, at.x, at.y, at.w, at.h, options.scale ?? 1, box.style.colorScheme);
4415
4599
  }
4416
4600
  else
4417
4601
  ctx.drawImage(image, at.x, at.y, at.w, at.h);
@@ -4499,7 +4683,8 @@ function fitted(box, x, y, w, h) {
4499
4683
  /** An inline `<svg>`, drawn in its content box. Its `currentColor` is the
4500
4684
  * box's `color`, as an icon's is the text's around it, and what it is
4501
4685
  * painted with the box's `fill` and `stroke`, where the document's style
4502
- * sheets set them: an icon set's `.icon { fill: currentColor }`. */
4686
+ * sheets set them: an icon set's `.icon { fill: currentColor }`. What
4687
+ * they give a shape inside it is asked for here (`BoxTree.shapeStyler`). */
4503
4688
  function paintSvg(ctx, box, options) {
4504
4689
  if (!box.el)
4505
4690
  return;
@@ -4511,7 +4696,7 @@ function paintSvg(ctx, box, options) {
4511
4696
  const paint = (value) => value === null || value === 'currentColor' || value === 'none'
4512
4697
  ? value
4513
4698
  : inkColor(value, style.color);
4514
- inlineDrawing(box.el).draw(ctx, x, y, w, h, options.scale ?? 1, style.color, paint(style.fill), paint(style.stroke));
4699
+ inlineDrawing(box.el).draw(ctx, x, y, w, h, options.scale ?? 1, style.color, paint(style.fill), paint(style.stroke), options.shapeStyler?.(box) ?? null);
4515
4700
  }
4516
4701
  /**
4517
4702
  * The X protocol carries glyph positions as Int16, so anything drawn past