@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/svg.ts
CHANGED
|
@@ -16,7 +16,12 @@ import { parseDocument } from 'htmlparser2';
|
|
|
16
16
|
import { Element, Text } from 'domhandler';
|
|
17
17
|
import type { ChildNode, ParentNode } from 'domhandler';
|
|
18
18
|
import * as ntk from 'react-x11/ntk';
|
|
19
|
-
import { isSvgRoot } from './dom.js';
|
|
19
|
+
import { isSvgRoot, rawTextOf } from './dom.js';
|
|
20
|
+
import { Cascade } from './css/cascade.js';
|
|
21
|
+
import { parseMediaQuery, parseStylesheet } from './css/parse.js';
|
|
22
|
+
import type { Stylesheet } from './css/parse.js';
|
|
23
|
+
import type { ShapeStyle, ShapeStyles } from './css/shapes.js';
|
|
24
|
+
import type { RootLook } from './css/style.js';
|
|
20
25
|
import { inkColor, isTransparent, parseColor } from './css/values.js';
|
|
21
26
|
|
|
22
27
|
export { isSvgRoot };
|
|
@@ -69,20 +74,6 @@ function absoluteLength(
|
|
|
69
74
|
return px >= 0 ? px : null;
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
/**
|
|
73
|
-
* The `width`/`height` of an inline `<svg>` as CSS: they are presentation
|
|
74
|
-
* attributes for the properties of the same names (SVG 2, 5.1.1), so
|
|
75
|
-
* `height="50%"` is a percentage of the containing block like a style's.
|
|
76
|
-
* A bare number is pixels; null for what CSS would not parse.
|
|
77
|
-
*/
|
|
78
|
-
export function svgSizeHint(value: string): string | null {
|
|
79
|
-
const v = value.trim();
|
|
80
|
-
if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?%$/i.test(v)) return v;
|
|
81
|
-
const m = LENGTH.exec(v);
|
|
82
|
-
if (!m) return null;
|
|
83
|
-
return m[2] ? v : `${parseFloat(m[1])}px`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
77
|
function viewBoxParts(el: Element): [number, number, number, number] | null {
|
|
87
78
|
const parts = (svgAttr(el, 'viewBox') ?? '')
|
|
88
79
|
.trim()
|
|
@@ -183,7 +174,8 @@ export class SvgDrawing {
|
|
|
183
174
|
private _seen = -1;
|
|
184
175
|
private _seenLast: ChildNode | null = null;
|
|
185
176
|
private _seenViewport = '';
|
|
186
|
-
/** The `fill` and `stroke` the view's tree was last given its root's
|
|
177
|
+
/** The `fill` and `stroke` the view's tree was last given its root's,
|
|
178
|
+
* and what the document's rules gave the elements in it. */
|
|
187
179
|
private _seenPaint = '';
|
|
188
180
|
/** Whether a length in the tree is a percentage of the viewport; null
|
|
189
181
|
* until the tree is first read. */
|
|
@@ -201,6 +193,16 @@ export class SvgDrawing {
|
|
|
201
193
|
/** The background an image's root gives its canvas; undefined until it
|
|
202
194
|
* is first read. */
|
|
203
195
|
private _canvas: string | null | undefined = undefined;
|
|
196
|
+
/** An SVG image's own style sheets, its `<style>` elements': read at its
|
|
197
|
+
* first draw, and none for most images. */
|
|
198
|
+
private _sheets: Stylesheet[] | null = null;
|
|
199
|
+
/** A cascade over them for each colour scheme and scale the image is
|
|
200
|
+
* drawn at, and what each gave it: by the viewport it was asked at where
|
|
201
|
+
* a sheet reads the viewport, and under one key where none does. */
|
|
202
|
+
private _cascades = new Map<
|
|
203
|
+
string,
|
|
204
|
+
{ cascade: Cascade; own: Map<string, ImagePaint | null> }
|
|
205
|
+
>();
|
|
204
206
|
|
|
205
207
|
constructor(root: Element, intrinsics: IntrinsicSize, standalone = false) {
|
|
206
208
|
this._root = root;
|
|
@@ -216,7 +218,8 @@ export class SvgDrawing {
|
|
|
216
218
|
* and `stroke` the root's own where the document's styles set them (SVG
|
|
217
219
|
* 2, 13.2): they are properties, which a style sheet's rule sets over
|
|
218
220
|
* the presentation attribute, and what is in the drawing inherits them
|
|
219
|
-
* from its root.
|
|
221
|
+
* from its root. `shapes` is what the rules give the elements inside it,
|
|
222
|
+
* the same way, where they give any.
|
|
220
223
|
*/
|
|
221
224
|
draw(
|
|
222
225
|
ctx: ClipContext,
|
|
@@ -228,6 +231,7 @@ export class SvgDrawing {
|
|
|
228
231
|
color?: string,
|
|
229
232
|
fill: string | null = null,
|
|
230
233
|
stroke: string | null = null,
|
|
234
|
+
shapes: ShapeStyles | null = null,
|
|
231
235
|
): void {
|
|
232
236
|
if (this._failed || !(w > 0 && h > 0)) return;
|
|
233
237
|
// the mock backend has no path API, and SvgView draws paths
|
|
@@ -261,8 +265,8 @@ export class SvgDrawing {
|
|
|
261
265
|
(fill === null ? '' : `fill:${fill};`) +
|
|
262
266
|
(stroke === null ? '' : `stroke:${stroke};`);
|
|
263
267
|
const view = box
|
|
264
|
-
? this._viewFor(box[2], box[3], paint)
|
|
265
|
-
: this._viewFor(w / scale, h / scale, paint);
|
|
268
|
+
? this._viewFor(box[2], box[3], paint, shapes)
|
|
269
|
+
: this._viewFor(w / scale, h / scale, paint, shapes);
|
|
266
270
|
if (!view) return;
|
|
267
271
|
ctx.save();
|
|
268
272
|
try {
|
|
@@ -312,11 +316,111 @@ export class SvgDrawing {
|
|
|
312
316
|
}
|
|
313
317
|
}
|
|
314
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Draw an SVG image — an `<img>`'s, a background's, a list marker's — as
|
|
321
|
+
* `draw` does, painted as its own style sheets say. An image is a
|
|
322
|
+
* document of its own: no rule of the page's reaches into it, and its
|
|
323
|
+
* `color` is not the page's. What its `<style>` elements say is given
|
|
324
|
+
* its elements as the page's rules are given a drawing inline, and its
|
|
325
|
+
* root its `color`, `fill` and `stroke`, which an inline root has from
|
|
326
|
+
* its box. `scheme` is the colour scheme of the element that embeds it:
|
|
327
|
+
* what `prefers-color-scheme` answers inside it, as Chrome has it.
|
|
328
|
+
*/
|
|
329
|
+
drawImage(
|
|
330
|
+
ctx: ClipContext,
|
|
331
|
+
x: number,
|
|
332
|
+
y: number,
|
|
333
|
+
w: number,
|
|
334
|
+
h: number,
|
|
335
|
+
scale: number,
|
|
336
|
+
scheme: 'light' | 'dark' = 'light',
|
|
337
|
+
): void {
|
|
338
|
+
if (this._failed || !(w > 0 && h > 0)) return;
|
|
339
|
+
const own = this._ownPaint(w, h, scale, scheme);
|
|
340
|
+
this.draw(
|
|
341
|
+
ctx,
|
|
342
|
+
x,
|
|
343
|
+
y,
|
|
344
|
+
w,
|
|
345
|
+
h,
|
|
346
|
+
scale,
|
|
347
|
+
own?.color,
|
|
348
|
+
own?.fill ?? null,
|
|
349
|
+
own?.stroke ?? null,
|
|
350
|
+
own?.shapes ?? null,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** What an image's own style sheets give it, drawn `w` by `h` device
|
|
355
|
+
* pixels, or null where it has none. */
|
|
356
|
+
private _ownPaint(
|
|
357
|
+
w: number,
|
|
358
|
+
h: number,
|
|
359
|
+
scale: number,
|
|
360
|
+
scheme: 'light' | 'dark',
|
|
361
|
+
): ImagePaint | null {
|
|
362
|
+
const sheets = (this._sheets ??= ownSheets(this._root));
|
|
363
|
+
if (!sheets.length) return null;
|
|
364
|
+
const made = `${scheme}|${scale}`;
|
|
365
|
+
let entry = this._cascades.get(made);
|
|
366
|
+
if (!entry) {
|
|
367
|
+
const cascade = new Cascade(
|
|
368
|
+
sheets,
|
|
369
|
+
imageLook(scheme, scale),
|
|
370
|
+
w,
|
|
371
|
+
h,
|
|
372
|
+
scale,
|
|
373
|
+
null,
|
|
374
|
+
null,
|
|
375
|
+
null,
|
|
376
|
+
null,
|
|
377
|
+
this._root,
|
|
378
|
+
);
|
|
379
|
+
entry = { cascade, own: new Map() };
|
|
380
|
+
this._cascades.set(made, entry);
|
|
381
|
+
}
|
|
382
|
+
const { cascade, own } = entry;
|
|
383
|
+
// An image's viewport is the rectangle it is drawn in, which is what a
|
|
384
|
+
// `@media (min-width)` in it, or a `vw`, is of: the answer is kept by
|
|
385
|
+
// that size only where a sheet asks.
|
|
386
|
+
const sized =
|
|
387
|
+
cascade.breakpoints.length > 0 ||
|
|
388
|
+
cascade.readsViewportWidth ||
|
|
389
|
+
cascade.readsViewportHeight;
|
|
390
|
+
const key = sized ? `${w}x${h}` : '';
|
|
391
|
+
const kept = own.get(key);
|
|
392
|
+
if (kept !== undefined) return kept;
|
|
393
|
+
cascade.viewportWidth = w;
|
|
394
|
+
cascade.viewportHeight = h;
|
|
395
|
+
let paint: ImagePaint | null = null;
|
|
396
|
+
try {
|
|
397
|
+
const root = this._root;
|
|
398
|
+
const style = cascade.styleFor(root, cascade.initial, false);
|
|
399
|
+
const ink = (value: string | null): string | null =>
|
|
400
|
+
value === null || value === 'currentColor' || value === 'none'
|
|
401
|
+
? value
|
|
402
|
+
: inkColor(value, style.color);
|
|
403
|
+
paint = {
|
|
404
|
+
color: style.color,
|
|
405
|
+
fill: ink(style.fill),
|
|
406
|
+
stroke: ink(style.stroke),
|
|
407
|
+
shapes: cascade.shapeStyles(root, style),
|
|
408
|
+
};
|
|
409
|
+
} catch {
|
|
410
|
+
// a sheet this cannot read leaves the image as its attributes draw it
|
|
411
|
+
}
|
|
412
|
+
// a background drawn at many sizes keeps a few of them
|
|
413
|
+
if (own.size >= 8) own.clear();
|
|
414
|
+
own.set(key, paint);
|
|
415
|
+
return paint;
|
|
416
|
+
}
|
|
417
|
+
|
|
315
418
|
private _viewFor(
|
|
316
419
|
width: number,
|
|
317
420
|
height: number,
|
|
318
421
|
/** The root's `fill` and `stroke` as declarations, or none. */
|
|
319
422
|
paint = '',
|
|
423
|
+
shapes: ShapeStyles | null = null,
|
|
320
424
|
): SvgViewLike | null {
|
|
321
425
|
const root = this._root;
|
|
322
426
|
const count = root.children.length;
|
|
@@ -332,11 +436,12 @@ export class SvgDrawing {
|
|
|
332
436
|
}
|
|
333
437
|
const sized = this._percent || this._uses;
|
|
334
438
|
const viewport = sized ? `${width}x${height}` : '';
|
|
439
|
+
const painted = shapes ? `${paint}|${shapes.key}` : paint;
|
|
335
440
|
if (
|
|
336
441
|
this._view &&
|
|
337
442
|
!grown &&
|
|
338
443
|
viewport === this._seenViewport &&
|
|
339
|
-
|
|
444
|
+
painted === this._seenPaint
|
|
340
445
|
) {
|
|
341
446
|
return this._view;
|
|
342
447
|
}
|
|
@@ -349,16 +454,16 @@ export class SvgDrawing {
|
|
|
349
454
|
const view = this._view ?? new View(null);
|
|
350
455
|
const missing = { any: false };
|
|
351
456
|
const tree =
|
|
352
|
-
sized || root.name.includes(':')
|
|
353
|
-
? copyTree(root, sized ? [width, height] : null, missing)
|
|
457
|
+
sized || shapes || root.name.includes(':')
|
|
458
|
+
? copyTree(root, sized ? [width, height] : null, missing, shapes)
|
|
354
459
|
: root;
|
|
355
|
-
view.setSvgDom(paint ?
|
|
460
|
+
view.setSvgDom(paint ? withPaint(tree, paint) : tree);
|
|
356
461
|
this._missingAt = missing.any ? lastNode(documentOf(root)) : undefined;
|
|
357
462
|
this._view = view;
|
|
358
463
|
this._seen = count;
|
|
359
464
|
this._seenLast = last;
|
|
360
465
|
this._seenViewport = viewport;
|
|
361
|
-
this._seenPaint =
|
|
466
|
+
this._seenPaint = painted;
|
|
362
467
|
return view;
|
|
363
468
|
} catch {
|
|
364
469
|
this._failed = true;
|
|
@@ -373,7 +478,7 @@ export class SvgDrawing {
|
|
|
373
478
|
* its `style` — where `SvgView` reads a root's paint, over its presentation
|
|
374
479
|
* attributes, for what is under it to inherit.
|
|
375
480
|
*/
|
|
376
|
-
function
|
|
481
|
+
function withPaint(root: Element, paint: string): Element {
|
|
377
482
|
const style = root.attribs.style;
|
|
378
483
|
return new Element(
|
|
379
484
|
root.name,
|
|
@@ -403,6 +508,75 @@ function canvasBackground(root: Element): string | null {
|
|
|
403
508
|
return found;
|
|
404
509
|
}
|
|
405
510
|
|
|
511
|
+
/** What an SVG image's own style sheets give it (`SvgDrawing.drawImage`):
|
|
512
|
+
* its root's `color`, `fill` and `stroke`, and what they give the
|
|
513
|
+
* elements in it. */
|
|
514
|
+
interface ImagePaint {
|
|
515
|
+
color: string;
|
|
516
|
+
fill: string | null;
|
|
517
|
+
stroke: string | null;
|
|
518
|
+
shapes: ShapeStyles | null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* The style sheets an SVG image's `<style>` elements hold, in order. One
|
|
523
|
+
* that names a type other than CSS's holds none (SVG 2, 6.3), and one with
|
|
524
|
+
* a `media` query holds its rules under it. Its text is its text and CDATA
|
|
525
|
+
* sections, as an XML parse leaves them, and not its comments.
|
|
526
|
+
*/
|
|
527
|
+
function ownSheets(root: Element): Stylesheet[] {
|
|
528
|
+
const sheets: Stylesheet[] = [];
|
|
529
|
+
const layers = new Map<string, number>();
|
|
530
|
+
let order = 0;
|
|
531
|
+
const stack: Element[] = [root];
|
|
532
|
+
for (let el = stack.pop(); el; el = stack.pop()) {
|
|
533
|
+
for (let i = el.children.length - 1; i >= 0; i -= 1) {
|
|
534
|
+
const child = el.children[i];
|
|
535
|
+
// an XML parse gives a `<style>` a type of its own, as HTML's does
|
|
536
|
+
if (child.type === 'tag' || child.type === 'style') {
|
|
537
|
+
stack.push(child as Element);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (el === root || localName(el.name) !== 'style') continue;
|
|
541
|
+
const type = (el.attribs.type ?? '').trim().toLowerCase();
|
|
542
|
+
if (type && type !== 'text/css') continue;
|
|
543
|
+
const media = el.attribs.media?.trim();
|
|
544
|
+
const sheet = parseStylesheet(
|
|
545
|
+
rawTextOf(el),
|
|
546
|
+
order,
|
|
547
|
+
layers,
|
|
548
|
+
null,
|
|
549
|
+
media ? [parseMediaQuery(media)] : null,
|
|
550
|
+
);
|
|
551
|
+
order += sheet.rules.length + 1;
|
|
552
|
+
sheets.push(sheet);
|
|
553
|
+
}
|
|
554
|
+
return sheets;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* The look an SVG image's own document starts from: CSS's initial values,
|
|
559
|
+
* black at `medium`, since nothing of the page's is inherited into an
|
|
560
|
+
* image — only the colour scheme, which its `prefers-color-scheme` answers.
|
|
561
|
+
*/
|
|
562
|
+
function imageLook(scheme: 'light' | 'dark', scale: number): RootLook {
|
|
563
|
+
return {
|
|
564
|
+
color: '#000000',
|
|
565
|
+
fontFamily: 'sans-serif',
|
|
566
|
+
fontSize: 16 * scale,
|
|
567
|
+
monoFamily: 'monospace',
|
|
568
|
+
linkColor: '#0000ee',
|
|
569
|
+
borderColor: '#000000',
|
|
570
|
+
mutedColor: '#808080',
|
|
571
|
+
background: 'transparent',
|
|
572
|
+
colorScheme: scheme,
|
|
573
|
+
surface: '#ffffff',
|
|
574
|
+
controlPadY: 0,
|
|
575
|
+
controlBorder: 0,
|
|
576
|
+
controlRadius: 0,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
406
580
|
/** Drawings of inline `<svg>` elements, per element: the element is the
|
|
407
581
|
* document's, and outlives the box trees built over it. */
|
|
408
582
|
const INLINE = new WeakMap<Element, SvgDrawing>();
|
|
@@ -609,11 +783,20 @@ const USE_PLACEMENT = new Set([
|
|
|
609
783
|
* viewport the `<use>` gives it — its `width` and `height`, and all of the
|
|
610
784
|
* drawing's where it has none. `missing.any` is set where one refers to an
|
|
611
785
|
* element the document does not have.
|
|
786
|
+
*
|
|
787
|
+
* And with what the document's rules give each element (`shapes`), where
|
|
788
|
+
* `SvgView` reads an element's own: at the end of its `style`, over its
|
|
789
|
+
* presentation attributes and over what that attribute says, which the
|
|
790
|
+
* cascade that made them has weighed already. An element a rule gives
|
|
791
|
+
* `display: none` is left out, with what is in it, and a shape one hides
|
|
792
|
+
* — `visibility` is inherited, and a shape in a hidden group may be shown
|
|
793
|
+
* again — is left out alone.
|
|
612
794
|
*/
|
|
613
795
|
function copyTree(
|
|
614
796
|
root: Element,
|
|
615
797
|
viewport: [number, number] | null,
|
|
616
798
|
missing?: { any: boolean },
|
|
799
|
+
shapes: ShapeStyles | null = null,
|
|
617
800
|
): Element {
|
|
618
801
|
const colon = root.name.indexOf(':');
|
|
619
802
|
const prefix = colon < 0 ? null : `${root.name.slice(0, colon)}:`;
|
|
@@ -627,6 +810,7 @@ function copyTree(
|
|
|
627
810
|
use: Element,
|
|
628
811
|
resolve: boolean,
|
|
629
812
|
depth: number,
|
|
813
|
+
hidden: boolean,
|
|
630
814
|
): Element | null => {
|
|
631
815
|
const id = useTarget(use);
|
|
632
816
|
if (id === null || depth >= USE_DEPTH) return null;
|
|
@@ -645,6 +829,8 @@ function copyTree(
|
|
|
645
829
|
for (const name in use.attribs) {
|
|
646
830
|
if (!USE_PLACEMENT.has(name)) attribs[name] = use.attribs[name];
|
|
647
831
|
}
|
|
832
|
+
const own = shapes?.of.get(use);
|
|
833
|
+
if (own) restyle(attribs, own);
|
|
648
834
|
const x = parseFloat(use.attribs.x ?? '') || 0;
|
|
649
835
|
const y = parseFloat(use.attribs.y ?? '') || 0;
|
|
650
836
|
let placed = '';
|
|
@@ -671,22 +857,52 @@ function copyTree(
|
|
|
671
857
|
// a length in a symbol with a `viewBox` is of that, and left as it is
|
|
672
858
|
const within = resolve && !box;
|
|
673
859
|
const children: ChildNode[] = [];
|
|
674
|
-
if (symbol)
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
} else children.push(copy(target, within, depth + 1));
|
|
860
|
+
if (symbol) copyInto(children, target, within, depth + 1, hidden);
|
|
861
|
+
else if (shown(target, hidden)) {
|
|
862
|
+
children.push(copy(target, within, depth + 1, hidden));
|
|
863
|
+
}
|
|
681
864
|
return new Element('g', attribs, children);
|
|
682
865
|
};
|
|
683
|
-
|
|
866
|
+
/** Whether an element is drawn at all, in a group that is hidden or
|
|
867
|
+
* not: one no rule gives `display: none`, and no shape a rule hides. */
|
|
868
|
+
const shown = (el: Element, hidden: boolean): boolean => {
|
|
869
|
+
if (!shapes) return true;
|
|
870
|
+
const own = shapes.of.get(el);
|
|
871
|
+
const name = localName(el.name);
|
|
872
|
+
if (own?.display === 'none' && !UNDRAWN.has(name)) return false;
|
|
873
|
+
return !(hiddenIn(own, hidden) && HIDEABLE.has(name));
|
|
874
|
+
};
|
|
875
|
+
const copyInto = (
|
|
876
|
+
children: ChildNode[],
|
|
877
|
+
el: Element,
|
|
878
|
+
resolve: boolean,
|
|
879
|
+
depth: number,
|
|
880
|
+
hidden: boolean,
|
|
881
|
+
): void => {
|
|
882
|
+
for (const child of el.children) {
|
|
883
|
+
if (child.type === 'tag') {
|
|
884
|
+
if (shown(child as Element, hidden)) {
|
|
885
|
+
children.push(copy(child as Element, resolve, depth, hidden));
|
|
886
|
+
}
|
|
887
|
+
} else if (child.type === 'text') children.push(new Text(child.data));
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
const copy = (
|
|
891
|
+
el: Element,
|
|
892
|
+
resolve: boolean,
|
|
893
|
+
depth = 0,
|
|
894
|
+
/** Whether the element inherits a `visibility` of `hidden`. */
|
|
895
|
+
hidden = false,
|
|
896
|
+
): Element => {
|
|
897
|
+
const own = shapes?.of.get(el);
|
|
898
|
+
hidden = hiddenIn(own, hidden);
|
|
684
899
|
if (localName(el.name) === 'use') {
|
|
685
|
-
const group = expand(el, resolve, depth);
|
|
900
|
+
const group = expand(el, resolve, depth, hidden);
|
|
686
901
|
if (group) return group;
|
|
687
902
|
}
|
|
688
903
|
const here = resolve && !OWN_UNITS.has(localName(el.name));
|
|
689
904
|
const attribs = { ...el.attribs };
|
|
905
|
+
if (own) restyle(attribs, own);
|
|
690
906
|
if (here && viewport && el !== root) {
|
|
691
907
|
for (const name in attribs) {
|
|
692
908
|
const axis = PERCENT_OF[name];
|
|
@@ -700,16 +916,79 @@ function copyTree(
|
|
|
700
916
|
}
|
|
701
917
|
}
|
|
702
918
|
const children: ChildNode[] = [];
|
|
703
|
-
|
|
704
|
-
if (child.type === 'tag') {
|
|
705
|
-
children.push(copy(child as Element, here, depth));
|
|
706
|
-
} else if (child.type === 'text') children.push(new Text(child.data));
|
|
707
|
-
}
|
|
919
|
+
copyInto(children, el, here, depth, hidden);
|
|
708
920
|
return new Element(strip(el.name), attribs, children);
|
|
709
921
|
};
|
|
710
922
|
return copy(root, true);
|
|
711
923
|
}
|
|
712
924
|
|
|
925
|
+
/** What `display: none` takes nothing from: `SvgView` draws none of them
|
|
926
|
+
* where they stand, and what refers to one still finds it (SVG 2, 5.3). */
|
|
927
|
+
const UNDRAWN = new Set([
|
|
928
|
+
'defs',
|
|
929
|
+
'symbol',
|
|
930
|
+
'lineargradient',
|
|
931
|
+
'radialgradient',
|
|
932
|
+
'stop',
|
|
933
|
+
'clippath',
|
|
934
|
+
'mask',
|
|
935
|
+
'pattern',
|
|
936
|
+
'marker',
|
|
937
|
+
'style',
|
|
938
|
+
]);
|
|
939
|
+
|
|
940
|
+
/** What `visibility: hidden` leaves undrawn: the elements with something
|
|
941
|
+
* of their own to draw, and a `<use>`, since what it draws inherits the
|
|
942
|
+
* property from it. */
|
|
943
|
+
const HIDEABLE = new Set([
|
|
944
|
+
'path',
|
|
945
|
+
'rect',
|
|
946
|
+
'circle',
|
|
947
|
+
'ellipse',
|
|
948
|
+
'line',
|
|
949
|
+
'polyline',
|
|
950
|
+
'polygon',
|
|
951
|
+
'text',
|
|
952
|
+
'image',
|
|
953
|
+
'use',
|
|
954
|
+
]);
|
|
955
|
+
|
|
956
|
+
/** Whether an element is hidden, given whether what it is in is. */
|
|
957
|
+
function hiddenIn(own: ShapeStyle | undefined, hidden: boolean): boolean {
|
|
958
|
+
const visibility = own?.visibility;
|
|
959
|
+
return visibility === undefined || visibility === 'inherit'
|
|
960
|
+
? hidden
|
|
961
|
+
: visibility === 'hidden';
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* An element's attributes with what the rules give it: each property at
|
|
966
|
+
* the end of its `style`, where `SvgView` reads it over the attribute of
|
|
967
|
+
* its name — but `opacity`, which it reads from the attribute alone. One
|
|
968
|
+
* the rules put back to what the element inherits is taken out of both.
|
|
969
|
+
*/
|
|
970
|
+
function restyle(attribs: Record<string, string>, own: ShapeStyle): void {
|
|
971
|
+
let style = attribs.style ?? '';
|
|
972
|
+
let added = '';
|
|
973
|
+
for (const prop in own) {
|
|
974
|
+
const value = own[prop];
|
|
975
|
+
if (prop === 'display' || prop === 'visibility') continue;
|
|
976
|
+
if (value === 'inherit') {
|
|
977
|
+
delete attribs[prop];
|
|
978
|
+
if (style) {
|
|
979
|
+
style = style
|
|
980
|
+
.split(';')
|
|
981
|
+
.filter((d) => d.slice(0, d.indexOf(':')).trim() !== prop)
|
|
982
|
+
.join(';');
|
|
983
|
+
}
|
|
984
|
+
} else if (prop === 'opacity') attribs.opacity = value;
|
|
985
|
+
else added += `${prop}:${value};`;
|
|
986
|
+
}
|
|
987
|
+
style = style && added ? `${style};${added}` : style || added;
|
|
988
|
+
if (style) attribs.style = style;
|
|
989
|
+
else delete attribs.style;
|
|
990
|
+
}
|
|
991
|
+
|
|
713
992
|
/**
|
|
714
993
|
* An SVG image's bytes as a drawing, or null for bytes that are not SVG —
|
|
715
994
|
* which is how a PNG or a JPEG goes on to the image decoder. Nothing names
|