@react-x11/components 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/html/controls.d.ts +41 -5
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +85 -57
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +134 -20
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +545 -126
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +18 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +27 -0
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/parse.d.ts +47 -6
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +336 -30
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +142 -15
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +563 -147
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/transform.d.ts +85 -0
- package/dist/html/css/transform.d.ts.map +1 -0
- package/dist/html/css/transform.js +425 -0
- package/dist/html/css/transform.js.map +1 -0
- package/dist/html/css/ua.d.ts +11 -1
- package/dist/html/css/ua.d.ts.map +1 -1
- package/dist/html/css/ua.js +73 -9
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +4 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/dom.d.ts.map +1 -1
- package/dist/html/dom.js +3 -1
- package/dist/html/dom.js.map +1 -1
- package/dist/html/fonts.d.ts +78 -4
- package/dist/html/fonts.d.ts.map +1 -1
- package/dist/html/fonts.js +373 -19
- package/dist/html/fonts.js.map +1 -1
- package/dist/html/form.d.ts +200 -0
- package/dist/html/form.d.ts.map +1 -0
- package/dist/html/form.js +801 -0
- package/dist/html/form.js.map +1 -0
- package/dist/html/index.d.ts +12 -0
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +30 -301
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/axes.d.ts +14 -0
- package/dist/html/layout/axes.d.ts.map +1 -0
- package/dist/html/layout/axes.js +28 -0
- package/dist/html/layout/axes.js.map +1 -0
- package/dist/html/layout/block.d.ts +93 -12
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +393 -39
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +90 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +74 -10
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +1 -1
- package/dist/html/layout/cache.d.ts.map +1 -1
- package/dist/html/layout/cache.js +6 -0
- package/dist/html/layout/cache.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +90 -18
- 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 +574 -84
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +3 -0
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +154 -54
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/multicol.d.ts +42 -0
- package/dist/html/layout/multicol.d.ts.map +1 -0
- package/dist/html/layout/multicol.js +897 -0
- package/dist/html/layout/multicol.js.map +1 -0
- package/dist/html/layout/shaping.d.ts +7 -0
- package/dist/html/layout/shaping.d.ts.map +1 -1
- package/dist/html/layout/shaping.js +25 -20
- package/dist/html/layout/shaping.js.map +1 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +62 -18
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +90 -15
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +771 -186
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +101 -5
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +1502 -160
- package/dist/html/paint.js.map +1 -1
- package/dist/html/svg.d.ts +15 -2
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +249 -17
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.d.ts +29 -0
- package/dist/html/widgets.d.ts.map +1 -0
- package/dist/html/widgets.js +700 -0
- package/dist/html/widgets.js.map +1 -0
- package/dist/html/woff2.d.ts +14 -0
- package/dist/html/woff2.d.ts.map +1 -0
- package/dist/html/woff2.js +608 -0
- package/dist/html/woff2.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/maps/controller.d.ts +17 -1
- package/dist/maps/controller.d.ts.map +1 -1
- package/dist/maps/controller.js +27 -7
- package/dist/maps/controller.js.map +1 -1
- package/dist/richtext/node.d.ts +8 -1
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +5 -1
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +25 -6
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +240 -24
- package/dist/richtext/runs.js.map +1 -1
- package/package.json +2 -2
- package/src/html/controls.ts +114 -51
- package/src/html/css/cascade.ts +614 -140
- package/src/html/css/color.ts +29 -0
- package/src/html/css/parse.ts +388 -33
- package/src/html/css/style.ts +688 -150
- package/src/html/css/transform.ts +459 -0
- package/src/html/css/ua.ts +77 -10
- package/src/html/css/values.ts +4 -0
- package/src/html/dom.ts +3 -1
- package/src/html/fonts.ts +417 -24
- package/src/html/form.ts +962 -0
- package/src/html/index.ts +49 -338
- package/src/html/layout/axes.ts +62 -0
- package/src/html/layout/block.ts +447 -37
- package/src/html/layout/boxes.ts +143 -14
- package/src/html/layout/cache.ts +6 -0
- package/src/html/layout/css-grid.ts +95 -22
- package/src/html/layout/flex.ts +675 -78
- package/src/html/layout/inline.ts +159 -55
- package/src/html/layout/multicol.ts +977 -0
- package/src/html/layout/shaping.ts +27 -18
- package/src/html/layout/table.ts +62 -19
- package/src/html/node.ts +798 -172
- package/src/html/paint.ts +1742 -241
- package/src/html/svg.ts +277 -19
- package/src/html/widgets.ts +821 -0
- package/src/html/woff2.ts +612 -0
- package/src/index.ts +1 -0
- package/src/maps/controller.ts +28 -7
- package/src/richtext/node.ts +13 -2
- package/src/richtext/runs.ts +313 -35
package/src/html/svg.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// as `preserveAspectRatio` says, and clipped to it.
|
|
15
15
|
import { parseDocument } from 'htmlparser2';
|
|
16
16
|
import { Element, Text } from 'domhandler';
|
|
17
|
-
import type { ChildNode } from 'domhandler';
|
|
17
|
+
import type { ChildNode, ParentNode } from 'domhandler';
|
|
18
18
|
import * as ntk from 'react-x11/ntk';
|
|
19
19
|
import { isSvgRoot } from './dom.js';
|
|
20
20
|
import { inkColor, isTransparent, parseColor } from './css/values.js';
|
|
@@ -183,9 +183,18 @@ export class SvgDrawing {
|
|
|
183
183
|
private _seen = -1;
|
|
184
184
|
private _seenLast: ChildNode | null = null;
|
|
185
185
|
private _seenViewport = '';
|
|
186
|
+
/** The `fill` and `stroke` the view's tree was last given its root's. */
|
|
187
|
+
private _seenPaint = '';
|
|
186
188
|
/** Whether a length in the tree is a percentage of the viewport; null
|
|
187
189
|
* until the tree is first read. */
|
|
188
190
|
private _percent: boolean | null = null;
|
|
191
|
+
/** Whether a `<use>` in the tree is one `SvgView` cannot draw as it
|
|
192
|
+
* stands (`needsExpanding`). */
|
|
193
|
+
private _uses = false;
|
|
194
|
+
/** The document's last node when a `<use>` was last looked up and its
|
|
195
|
+
* element not found: a streamed document may bring it yet, and has
|
|
196
|
+
* when its last node is another. Undefined where none was missing. */
|
|
197
|
+
private _missingAt: ChildNode | null | undefined = undefined;
|
|
189
198
|
private _failed = false;
|
|
190
199
|
/** An SVG image's own document, rather than an element of this one. */
|
|
191
200
|
private readonly _standalone: boolean;
|
|
@@ -203,7 +212,11 @@ export class SvgDrawing {
|
|
|
203
212
|
* Draw into a rectangle — the box's content box, in device pixels —
|
|
204
213
|
* clipped to it. The `viewBox`, where there is one, is fitted to it as
|
|
205
214
|
* `preserveAspectRatio` says; without one a user unit is a CSS pixel,
|
|
206
|
-
* which is `scale` device pixels. `color` is `currentColor
|
|
215
|
+
* which is `scale` device pixels. `color` is `currentColor`, and `fill`
|
|
216
|
+
* and `stroke` the root's own where the document's styles set them (SVG
|
|
217
|
+
* 2, 13.2): they are properties, which a style sheet's rule sets over
|
|
218
|
+
* the presentation attribute, and what is in the drawing inherits them
|
|
219
|
+
* from its root.
|
|
207
220
|
*/
|
|
208
221
|
draw(
|
|
209
222
|
ctx: ClipContext,
|
|
@@ -213,6 +226,8 @@ export class SvgDrawing {
|
|
|
213
226
|
h: number,
|
|
214
227
|
scale: number,
|
|
215
228
|
color?: string,
|
|
229
|
+
fill: string | null = null,
|
|
230
|
+
stroke: string | null = null,
|
|
216
231
|
): void {
|
|
217
232
|
if (this._failed || !(w > 0 && h > 0)) return;
|
|
218
233
|
// the mock backend has no path API, and SvgView draws paths
|
|
@@ -242,9 +257,12 @@ export class SvgDrawing {
|
|
|
242
257
|
}
|
|
243
258
|
const box = viewBoxOf(root);
|
|
244
259
|
// the viewport in user units, which a percentage is of
|
|
260
|
+
const paint =
|
|
261
|
+
(fill === null ? '' : `fill:${fill};`) +
|
|
262
|
+
(stroke === null ? '' : `stroke:${stroke};`);
|
|
245
263
|
const view = box
|
|
246
|
-
? this._viewFor(box[2], box[3])
|
|
247
|
-
: this._viewFor(w / scale, h / scale);
|
|
264
|
+
? this._viewFor(box[2], box[3], paint)
|
|
265
|
+
: this._viewFor(w / scale, h / scale, paint);
|
|
248
266
|
if (!view) return;
|
|
249
267
|
ctx.save();
|
|
250
268
|
try {
|
|
@@ -294,14 +312,32 @@ export class SvgDrawing {
|
|
|
294
312
|
}
|
|
295
313
|
}
|
|
296
314
|
|
|
297
|
-
private _viewFor(
|
|
315
|
+
private _viewFor(
|
|
316
|
+
width: number,
|
|
317
|
+
height: number,
|
|
318
|
+
/** The root's `fill` and `stroke` as declarations, or none. */
|
|
319
|
+
paint = '',
|
|
320
|
+
): SvgViewLike | null {
|
|
298
321
|
const root = this._root;
|
|
299
322
|
const count = root.children.length;
|
|
300
323
|
const last = root.lastChild;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
324
|
+
// more of the document has arrived since a `<use>` found nothing
|
|
325
|
+
const arrived =
|
|
326
|
+
this._missingAt !== undefined &&
|
|
327
|
+
lastNode(documentOf(root)) !== this._missingAt;
|
|
328
|
+
const grown = count !== this._seen || last !== this._seenLast || arrived;
|
|
329
|
+
if (grown || this._percent === null) {
|
|
330
|
+
this._percent = hasPercent(root);
|
|
331
|
+
this._uses = needsExpanding(root);
|
|
332
|
+
}
|
|
333
|
+
const sized = this._percent || this._uses;
|
|
334
|
+
const viewport = sized ? `${width}x${height}` : '';
|
|
335
|
+
if (
|
|
336
|
+
this._view &&
|
|
337
|
+
!grown &&
|
|
338
|
+
viewport === this._seenViewport &&
|
|
339
|
+
paint === this._seenPaint
|
|
340
|
+
) {
|
|
305
341
|
return this._view;
|
|
306
342
|
}
|
|
307
343
|
const View = svgViewClass();
|
|
@@ -311,15 +347,18 @@ export class SvgDrawing {
|
|
|
311
347
|
}
|
|
312
348
|
try {
|
|
313
349
|
const view = this._view ?? new View(null);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
350
|
+
const missing = { any: false };
|
|
351
|
+
const tree =
|
|
352
|
+
sized || root.name.includes(':')
|
|
353
|
+
? copyTree(root, sized ? [width, height] : null, missing)
|
|
354
|
+
: root;
|
|
355
|
+
view.setSvgDom(paint ? painted(tree, paint) : tree);
|
|
356
|
+
this._missingAt = missing.any ? lastNode(documentOf(root)) : undefined;
|
|
319
357
|
this._view = view;
|
|
320
358
|
this._seen = count;
|
|
321
359
|
this._seenLast = last;
|
|
322
360
|
this._seenViewport = viewport;
|
|
361
|
+
this._seenPaint = paint;
|
|
323
362
|
return view;
|
|
324
363
|
} catch {
|
|
325
364
|
this._failed = true;
|
|
@@ -328,6 +367,21 @@ export class SvgDrawing {
|
|
|
328
367
|
}
|
|
329
368
|
}
|
|
330
369
|
|
|
370
|
+
/**
|
|
371
|
+
* A drawing's root with its `fill` and `stroke` the document's: a copy of
|
|
372
|
+
* the root, its children the same ones, with the declarations at the end of
|
|
373
|
+
* its `style` — where `SvgView` reads a root's paint, over its presentation
|
|
374
|
+
* attributes, for what is under it to inherit.
|
|
375
|
+
*/
|
|
376
|
+
function painted(root: Element, paint: string): Element {
|
|
377
|
+
const style = root.attribs.style;
|
|
378
|
+
return new Element(
|
|
379
|
+
root.name,
|
|
380
|
+
{ ...root.attribs, style: style ? `${style};${paint}` : paint },
|
|
381
|
+
root.children,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
331
385
|
/**
|
|
332
386
|
* The colour an SVG image's root element sets as its background, in its
|
|
333
387
|
* `style` attribute — the one place an image can say it, since
|
|
@@ -413,13 +467,154 @@ function hasPercent(root: Element): boolean {
|
|
|
413
467
|
return walk(root);
|
|
414
468
|
}
|
|
415
469
|
|
|
470
|
+
/** The element a `<use>` refers to by a fragment, its id: one in another
|
|
471
|
+
* document is none here, where nothing is fetched for a drawing. */
|
|
472
|
+
function useTarget(el: Element): string | null {
|
|
473
|
+
const href = el.attribs.href ?? el.attribs['xlink:href'] ?? '';
|
|
474
|
+
return href.length > 1 && href.startsWith('#') ? href.slice(1) : null;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** The elements under a root by their ids, the first of each. */
|
|
478
|
+
function idsUnder(root: Element): Map<string, Element> {
|
|
479
|
+
const ids = new Map<string, Element>();
|
|
480
|
+
const walk = (el: Element): void => {
|
|
481
|
+
for (const child of el.children) {
|
|
482
|
+
if (child.type !== 'tag') continue;
|
|
483
|
+
const tag = child as Element;
|
|
484
|
+
const id = tag.attribs.id;
|
|
485
|
+
if (id && !ids.has(id)) ids.set(id, tag);
|
|
486
|
+
walk(tag);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
walk(root);
|
|
490
|
+
return ids;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Whether a `<use>` under the root is one `SvgView` cannot draw as it
|
|
495
|
+
* stands: it looks a reference up among the root's own elements, and draws
|
|
496
|
+
* a `<symbol>` as its children where they are. So one to an element
|
|
497
|
+
* outside the root — an icon sprite's, a hidden `<svg>` of symbols at the
|
|
498
|
+
* top of the page — and one to a symbol with a `viewBox` of its own are
|
|
499
|
+
* drawn from a copy (`copyTree`).
|
|
500
|
+
*/
|
|
501
|
+
function needsExpanding(root: Element): boolean {
|
|
502
|
+
let ids: Map<string, Element> | null = null;
|
|
503
|
+
const walk = (el: Element): boolean => {
|
|
504
|
+
for (const child of el.children) {
|
|
505
|
+
if (child.type !== 'tag') continue;
|
|
506
|
+
const tag = child as Element;
|
|
507
|
+
if (localName(tag.name) === 'use') {
|
|
508
|
+
const id = useTarget(tag);
|
|
509
|
+
if (id !== null) {
|
|
510
|
+
ids ??= idsUnder(root);
|
|
511
|
+
const target = ids.get(id);
|
|
512
|
+
if (!target) return true;
|
|
513
|
+
if (localName(target.name) === 'symbol' && viewBoxOf(target)) {
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (walk(tag)) return true;
|
|
519
|
+
}
|
|
520
|
+
return false;
|
|
521
|
+
};
|
|
522
|
+
return walk(root);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function documentOf(el: Element): ParentNode {
|
|
526
|
+
let top: ParentNode = el;
|
|
527
|
+
while (top.parent) top = top.parent;
|
|
528
|
+
return top;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** The last node of a document, in its order: what a document that is
|
|
532
|
+
* still arriving has another of once more of it has. */
|
|
533
|
+
function lastNode(top: ParentNode): ChildNode | null {
|
|
534
|
+
let last: ChildNode | null = top.lastChild;
|
|
535
|
+
while (last && 'lastChild' in last && last.lastChild) last = last.lastChild;
|
|
536
|
+
return last;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** The first element of a document with an id, in its order. */
|
|
540
|
+
function elementById(top: ParentNode, id: string): Element | null {
|
|
541
|
+
const stack: ChildNode[] = [...top.children].reverse();
|
|
542
|
+
for (let node = stack.pop(); node; node = stack.pop()) {
|
|
543
|
+
if (node.type !== 'tag') continue;
|
|
544
|
+
const el = node as Element;
|
|
545
|
+
if (el.attribs.id === id) return el;
|
|
546
|
+
for (let i = el.children.length - 1; i >= 0; i -= 1) {
|
|
547
|
+
stack.push(el.children[i]);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** How deep a `<use>` may be of a `<use>`: one that reaches itself stops. */
|
|
554
|
+
const USE_DEPTH = 8;
|
|
555
|
+
|
|
556
|
+
/** A `<use>`'s `width` or `height`, in user units: a number, or a
|
|
557
|
+
* percentage of the viewport's; null for none, and for `auto`. */
|
|
558
|
+
function useLength(value: string | undefined, of: number): number | null {
|
|
559
|
+
if (value === undefined) return null;
|
|
560
|
+
const n = parseFloat(value);
|
|
561
|
+
if (!Number.isFinite(n) || n < 0) return null;
|
|
562
|
+
return value.trim().endsWith('%') ? (n / 100) * of : n;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** The transform that fits a `viewBox` into a viewport at `x`, `y`, as
|
|
566
|
+
* `preserveAspectRatio` says (SVG 2, 8.2). */
|
|
567
|
+
function fitTransform(
|
|
568
|
+
box: [number, number, number, number],
|
|
569
|
+
x: number,
|
|
570
|
+
y: number,
|
|
571
|
+
width: number,
|
|
572
|
+
height: number,
|
|
573
|
+
preserve: string | undefined,
|
|
574
|
+
): string {
|
|
575
|
+
const fit = (preserve ?? '').trim().split(/\s+/);
|
|
576
|
+
if (fit[0] === 'defer') fit.shift();
|
|
577
|
+
let sx = width / box[2];
|
|
578
|
+
let sy = height / box[3];
|
|
579
|
+
if (fit[0] !== 'none') {
|
|
580
|
+
const align = ALIGN.exec(fit[0] ?? '') ?? ['xMidYMid', 'Mid', 'Mid'];
|
|
581
|
+
const scale = fit[1] === 'slice' ? Math.max(sx, sy) : Math.min(sx, sy);
|
|
582
|
+
x += (width - box[2] * scale) * AT[align[1]];
|
|
583
|
+
y += (height - box[3] * scale) * AT[align[2]];
|
|
584
|
+
sx = scale;
|
|
585
|
+
sy = scale;
|
|
586
|
+
}
|
|
587
|
+
return `translate(${x - box[0] * sx},${y - box[1] * sy}) scale(${sx},${sy})`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** What a `<use>` says of where its element goes, and not of how it looks:
|
|
591
|
+
* the rest is the group's the element is drawn in. */
|
|
592
|
+
const USE_PLACEMENT = new Set([
|
|
593
|
+
'href',
|
|
594
|
+
'xlink:href',
|
|
595
|
+
'x',
|
|
596
|
+
'y',
|
|
597
|
+
'width',
|
|
598
|
+
'height',
|
|
599
|
+
]);
|
|
600
|
+
|
|
416
601
|
/**
|
|
417
602
|
* The tree `SvgView` reads, where the document's own will not do: with
|
|
418
603
|
* local names — `SvgView` knows `rect`, not `svg:rect`, and a prefix is
|
|
419
604
|
* dropped only where it is bound to SVG — and with its percentages resolved
|
|
420
|
-
* against a viewport of `[width, height]` user units, where one is given
|
|
605
|
+
* against a viewport of `[width, height]` user units, where one is given;
|
|
606
|
+
* and with each `<use>` that `SvgView` cannot draw (`needsExpanding`) as a
|
|
607
|
+
* group of what it refers to (SVG 2, 5.5): the element, from wherever in
|
|
608
|
+
* the document it is, or a symbol's children, its `viewBox` fitted to the
|
|
609
|
+
* viewport the `<use>` gives it — its `width` and `height`, and all of the
|
|
610
|
+
* drawing's where it has none. `missing.any` is set where one refers to an
|
|
611
|
+
* element the document does not have.
|
|
421
612
|
*/
|
|
422
|
-
function copyTree(
|
|
613
|
+
function copyTree(
|
|
614
|
+
root: Element,
|
|
615
|
+
viewport: [number, number] | null,
|
|
616
|
+
missing?: { any: boolean },
|
|
617
|
+
): Element {
|
|
423
618
|
const colon = root.name.indexOf(':');
|
|
424
619
|
const prefix = colon < 0 ? null : `${root.name.slice(0, colon)}:`;
|
|
425
620
|
const strip = (name: string): string =>
|
|
@@ -427,7 +622,69 @@ function copyTree(root: Element, viewport: [number, number] | null): Element {
|
|
|
427
622
|
const diagonal = viewport
|
|
428
623
|
? Math.sqrt((viewport[0] ** 2 + viewport[1] ** 2) / 2)
|
|
429
624
|
: 0;
|
|
430
|
-
|
|
625
|
+
let ids: Map<string, Element> | null = null;
|
|
626
|
+
const expand = (
|
|
627
|
+
use: Element,
|
|
628
|
+
resolve: boolean,
|
|
629
|
+
depth: number,
|
|
630
|
+
): Element | null => {
|
|
631
|
+
const id = useTarget(use);
|
|
632
|
+
if (id === null || depth >= USE_DEPTH) return null;
|
|
633
|
+
ids ??= idsUnder(root);
|
|
634
|
+
const inside = ids.get(id);
|
|
635
|
+
const target = inside ?? elementById(documentOf(root), id);
|
|
636
|
+
if (!target) {
|
|
637
|
+
if (missing) missing.any = true;
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
const symbol = localName(target.name) === 'symbol';
|
|
641
|
+
const box = symbol ? viewBoxOf(target) : null;
|
|
642
|
+
// one of the root's own with no viewport to fit: `SvgView` draws it
|
|
643
|
+
if (inside && !box) return null;
|
|
644
|
+
const attribs: Record<string, string> = {};
|
|
645
|
+
for (const name in use.attribs) {
|
|
646
|
+
if (!USE_PLACEMENT.has(name)) attribs[name] = use.attribs[name];
|
|
647
|
+
}
|
|
648
|
+
const x = parseFloat(use.attribs.x ?? '') || 0;
|
|
649
|
+
const y = parseFloat(use.attribs.y ?? '') || 0;
|
|
650
|
+
let placed = '';
|
|
651
|
+
if (box && viewport) {
|
|
652
|
+
const width =
|
|
653
|
+
useLength(use.attribs.width, viewport[0]) ??
|
|
654
|
+
useLength(target.attribs.width, viewport[0]) ??
|
|
655
|
+
viewport[0];
|
|
656
|
+
const height =
|
|
657
|
+
useLength(use.attribs.height, viewport[1]) ??
|
|
658
|
+
useLength(target.attribs.height, viewport[1]) ??
|
|
659
|
+
viewport[1];
|
|
660
|
+
placed = fitTransform(
|
|
661
|
+
box,
|
|
662
|
+
x,
|
|
663
|
+
y,
|
|
664
|
+
width,
|
|
665
|
+
height,
|
|
666
|
+
svgAttr(target, 'preserveAspectRatio'),
|
|
667
|
+
);
|
|
668
|
+
} else if (x || y) placed = `translate(${x},${y})`;
|
|
669
|
+
const transform = `${attribs.transform ?? ''} ${placed}`.trim();
|
|
670
|
+
if (transform) attribs.transform = transform;
|
|
671
|
+
// a length in a symbol with a `viewBox` is of that, and left as it is
|
|
672
|
+
const within = resolve && !box;
|
|
673
|
+
const children: ChildNode[] = [];
|
|
674
|
+
if (symbol) {
|
|
675
|
+
for (const child of target.children) {
|
|
676
|
+
if (child.type === 'tag') {
|
|
677
|
+
children.push(copy(child as Element, within, depth + 1));
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
} else children.push(copy(target, within, depth + 1));
|
|
681
|
+
return new Element('g', attribs, children);
|
|
682
|
+
};
|
|
683
|
+
const copy = (el: Element, resolve: boolean, depth = 0): Element => {
|
|
684
|
+
if (localName(el.name) === 'use') {
|
|
685
|
+
const group = expand(el, resolve, depth);
|
|
686
|
+
if (group) return group;
|
|
687
|
+
}
|
|
431
688
|
const here = resolve && !OWN_UNITS.has(localName(el.name));
|
|
432
689
|
const attribs = { ...el.attribs };
|
|
433
690
|
if (here && viewport && el !== root) {
|
|
@@ -444,8 +701,9 @@ function copyTree(root: Element, viewport: [number, number] | null): Element {
|
|
|
444
701
|
}
|
|
445
702
|
const children: ChildNode[] = [];
|
|
446
703
|
for (const child of el.children) {
|
|
447
|
-
if (child.type === 'tag')
|
|
448
|
-
|
|
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));
|
|
449
707
|
}
|
|
450
708
|
return new Element(strip(el.name), attribs, children);
|
|
451
709
|
};
|