@react-x11/components 0.14.1 → 0.16.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/code-editor/node.d.ts +13 -0
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +23 -5
- package/dist/code-editor/node.js.map +1 -1
- package/dist/html/css/cascade.d.ts +171 -26
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +616 -216
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +25 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +307 -19
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/content.d.ts +11 -6
- package/dist/html/css/content.d.ts.map +1 -1
- package/dist/html/css/content.js +171 -12
- package/dist/html/css/content.js.map +1 -1
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +62 -9
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/shapes.d.ts +18 -0
- package/dist/html/css/shapes.d.ts.map +1 -1
- package/dist/html/css/shapes.js +21 -1
- package/dist/html/css/shapes.js.map +1 -1
- package/dist/html/css/style.d.ts +42 -5
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +74 -12
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +15 -0
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +5 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +18 -9
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +7 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +29 -9
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +141 -52
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +16 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +144 -23
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +11 -9
- package/dist/html/layout/css-grid.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +1631 -221
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +15 -3
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +23 -8
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/table.d.ts +27 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +886 -324
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +35 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +118 -26
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +558 -178
- package/dist/html/paint.js.map +1 -1
- package/dist/html/resources.js +14 -1
- package/dist/html/resources.js.map +1 -1
- package/dist/html/svg.d.ts +23 -7
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +264 -59
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.d.ts.map +1 -1
- package/dist/html/widgets.js +57 -4
- package/dist/html/widgets.js.map +1 -1
- package/package.json +3 -3
- package/src/code-editor/node.ts +24 -5
- package/src/html/css/cascade.ts +697 -221
- package/src/html/css/color.ts +345 -18
- package/src/html/css/content.ts +173 -15
- package/src/html/css/parse.ts +60 -8
- package/src/html/css/shapes.ts +35 -1
- package/src/html/css/style.ts +122 -17
- package/src/html/css/ua.ts +15 -0
- package/src/html/css/values.ts +23 -9
- package/src/html/index.ts +7 -0
- package/src/html/layout/block.ts +149 -58
- package/src/html/layout/boxes.ts +171 -27
- package/src/html/layout/css-grid.ts +12 -9
- package/src/html/layout/flex.ts +1843 -212
- package/src/html/layout/inline.ts +27 -7
- package/src/html/layout/table.ts +961 -332
- package/src/html/node.ts +151 -27
- package/src/html/paint.ts +692 -234
- package/src/html/resources.ts +13 -1
- package/src/html/svg.ts +299 -56
- package/src/html/widgets.ts +61 -4
package/dist/html/svg.js
CHANGED
|
@@ -18,6 +18,7 @@ import * as ntk from 'react-x11/ntk';
|
|
|
18
18
|
import { isSvgRoot, rawTextOf } from './dom.js';
|
|
19
19
|
import { Cascade } from './css/cascade.js';
|
|
20
20
|
import { parseMediaQuery, parseStylesheet } from './css/parse.js';
|
|
21
|
+
import { useHref } from './css/shapes.js';
|
|
21
22
|
import { inkColor, isTransparent, parseColor } from './css/values.js';
|
|
22
23
|
export { isSvgRoot };
|
|
23
24
|
function svgAttr(el, name) {
|
|
@@ -102,10 +103,93 @@ export function svgIntrinsics(el, fontSize = 16) {
|
|
|
102
103
|
}
|
|
103
104
|
return { width, height, ratio };
|
|
104
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* An image's size, in device pixels, where nothing sets it: CSS Images 3's
|
|
108
|
+
* default sizing algorithm (5.3.3), against a default object size of
|
|
109
|
+
* `width` by `height` device pixels — a background's positioning area, a
|
|
110
|
+
* list marker's square. Its own size where it has one; the dimension it
|
|
111
|
+
* lacks from its ratio, or else from the default; and one with a ratio
|
|
112
|
+
* alone as large as fits in the default. An SVG may be any of these, and a
|
|
113
|
+
* raster image is the first. `scale` is device pixels per CSS pixel, which
|
|
114
|
+
* the image's own size is in.
|
|
115
|
+
*/
|
|
116
|
+
export function concreteSize(size, width, height, scale) {
|
|
117
|
+
const { ratio } = size;
|
|
118
|
+
const w = size.width === null ? null : size.width * scale;
|
|
119
|
+
const h = size.height === null ? null : size.height * scale;
|
|
120
|
+
if (w !== null && h !== null)
|
|
121
|
+
return [w, h];
|
|
122
|
+
if (w !== null)
|
|
123
|
+
return [w, ratio > 0 ? w / ratio : height];
|
|
124
|
+
if (h !== null)
|
|
125
|
+
return [ratio > 0 ? h * ratio : width, h];
|
|
126
|
+
if (ratio > 0) {
|
|
127
|
+
return width / height > ratio
|
|
128
|
+
? [height * ratio, height]
|
|
129
|
+
: [width, width / ratio];
|
|
130
|
+
}
|
|
131
|
+
return [width, height];
|
|
132
|
+
}
|
|
105
133
|
function svgViewClass() {
|
|
106
134
|
const ctor = ntk.SvgView;
|
|
107
135
|
return typeof ctor === 'function' ? ctor : null;
|
|
108
136
|
}
|
|
137
|
+
const LENT = new WeakMap();
|
|
138
|
+
/**
|
|
139
|
+
* The context a drawing is lent: the same context, keeping count of the
|
|
140
|
+
* saves the drawing makes on it, so that whatever it leaves saved can be
|
|
141
|
+
* restored after it, and making none of the restores it makes past them.
|
|
142
|
+
*
|
|
143
|
+
* `SvgView` saves the context as it starts to draw, and around an element
|
|
144
|
+
* with a `transform`, and restores in no `finally`: an element it throws
|
|
145
|
+
* on — a colour it cannot read, `fill="var(--c)"` — leaves those saves
|
|
146
|
+
* open. The restore after the drawing then took the last of them for its
|
|
147
|
+
* own save, and the clip to the drawing's box was left on the window's
|
|
148
|
+
* context: everything a later paint drew, in that frame and every frame
|
|
149
|
+
* after, was cut to a sixteen-pixel icon. nextjs.org's blog drew its
|
|
150
|
+
* heading, the icon beside it, and nothing more.
|
|
151
|
+
*/
|
|
152
|
+
function lend(ctx) {
|
|
153
|
+
const known = LENT.get(ctx);
|
|
154
|
+
if (known)
|
|
155
|
+
return known;
|
|
156
|
+
const bound = new WeakMap();
|
|
157
|
+
const save = () => {
|
|
158
|
+
ctx.save();
|
|
159
|
+
lent.open += 1;
|
|
160
|
+
};
|
|
161
|
+
const restore = () => {
|
|
162
|
+
if (lent.open === 0)
|
|
163
|
+
return;
|
|
164
|
+
lent.open -= 1;
|
|
165
|
+
ctx.restore();
|
|
166
|
+
};
|
|
167
|
+
const lent = {
|
|
168
|
+
open: 0,
|
|
169
|
+
ctx: new Proxy(ctx, {
|
|
170
|
+
get(target, key) {
|
|
171
|
+
if (key === 'save')
|
|
172
|
+
return save;
|
|
173
|
+
if (key === 'restore')
|
|
174
|
+
return restore;
|
|
175
|
+
// on the context itself: an accessor, a method and a private field
|
|
176
|
+
// all see the context they belong to
|
|
177
|
+
const value = Reflect.get(target, key);
|
|
178
|
+
if (typeof value !== 'function')
|
|
179
|
+
return value;
|
|
180
|
+
let method = bound.get(value);
|
|
181
|
+
if (!method) {
|
|
182
|
+
method = value.bind(target);
|
|
183
|
+
bound.set(value, method);
|
|
184
|
+
}
|
|
185
|
+
return method;
|
|
186
|
+
},
|
|
187
|
+
set: (target, key, value) => Reflect.set(target, key, value),
|
|
188
|
+
}),
|
|
189
|
+
};
|
|
190
|
+
LENT.set(ctx, lent);
|
|
191
|
+
return lent;
|
|
192
|
+
}
|
|
109
193
|
const ALIGN = /^x(Min|Mid|Max)Y(Min|Mid|Max)$/;
|
|
110
194
|
const AT = { Min: 0, Mid: 0.5, Max: 1 };
|
|
111
195
|
/**
|
|
@@ -117,12 +201,14 @@ export class SvgDrawing {
|
|
|
117
201
|
intrinsics;
|
|
118
202
|
_root;
|
|
119
203
|
_view = null;
|
|
120
|
-
/** What the view was last handed the tree for: the root's child count
|
|
121
|
-
* and
|
|
122
|
-
*
|
|
123
|
-
*
|
|
204
|
+
/** What the view was last handed the tree for: the root's child count,
|
|
205
|
+
* and where the root and each element outside it a `<use>` drew ended —
|
|
206
|
+
* a streamed document grows an inline drawing after its first paint,
|
|
207
|
+
* and a sprite's symbol after the first icon drawn from it — and, for a
|
|
208
|
+
* drawing with percentages in it, the viewport they were resolved
|
|
209
|
+
* against. */
|
|
124
210
|
_seen = -1;
|
|
125
|
-
|
|
211
|
+
_seenEnds = [];
|
|
126
212
|
_seenViewport = '';
|
|
127
213
|
/** The `fill` and `stroke` the view's tree was last given its root's,
|
|
128
214
|
* and what the document's rules gave the elements in it. */
|
|
@@ -150,10 +236,14 @@ export class SvgDrawing {
|
|
|
150
236
|
* drawn at, and what each gave it: by the viewport it was asked at where
|
|
151
237
|
* a sheet reads the viewport, and under one key where none does. */
|
|
152
238
|
_cascades = new Map();
|
|
153
|
-
|
|
239
|
+
/** The element an SVG image's URL names by its fragment, its own
|
|
240
|
+
* `:target`: a sprite sheet shows the icon `image.svg#icon` names. */
|
|
241
|
+
_target;
|
|
242
|
+
constructor(root, intrinsics, standalone = false, target = null) {
|
|
154
243
|
this._root = root;
|
|
155
244
|
this.intrinsics = intrinsics;
|
|
156
245
|
this._standalone = standalone;
|
|
246
|
+
this._target = target;
|
|
157
247
|
}
|
|
158
248
|
/**
|
|
159
249
|
* Draw into a rectangle — the box's content box, in device pixels —
|
|
@@ -198,21 +288,45 @@ export class SvgDrawing {
|
|
|
198
288
|
}
|
|
199
289
|
const box = viewBoxOf(root);
|
|
200
290
|
// the viewport in user units, which a percentage is of
|
|
291
|
+
let vw = w / scale;
|
|
292
|
+
let vh = h / scale;
|
|
293
|
+
// An image with no `viewBox` is laid out at its own size, along an
|
|
294
|
+
// axis it has one on, and stretched to the rectangle along it, as a
|
|
295
|
+
// raster image is: Blink gives it a `viewBox` of its own size that
|
|
296
|
+
// `preserveAspectRatio: none` fits. Drawn at its own size, an icon of
|
|
297
|
+
// `width="24" height="24"` shown 48 wide was a quarter of its box.
|
|
298
|
+
let kx = 1;
|
|
299
|
+
let ky = 1;
|
|
300
|
+
if (!box && this._standalone) {
|
|
301
|
+
const own = this.intrinsics;
|
|
302
|
+
if (own.width !== null && own.width > 0) {
|
|
303
|
+
kx = vw / own.width;
|
|
304
|
+
vw = own.width;
|
|
305
|
+
}
|
|
306
|
+
if (own.height !== null && own.height > 0) {
|
|
307
|
+
ky = vh / own.height;
|
|
308
|
+
vh = own.height;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
201
311
|
const paint = (fill === null ? '' : `fill:${fill};`) +
|
|
202
312
|
(stroke === null ? '' : `stroke:${stroke};`);
|
|
203
313
|
const view = box
|
|
204
314
|
? this._viewFor(box[2], box[3], paint, shapes)
|
|
205
|
-
: this._viewFor(
|
|
315
|
+
: this._viewFor(vw, vh, paint, shapes);
|
|
206
316
|
if (!view)
|
|
207
317
|
return;
|
|
208
318
|
ctx.save();
|
|
319
|
+
// the view draws on the context lent, which counts its saves
|
|
320
|
+
const lent = lend(ctx);
|
|
321
|
+
const outer = lent.open;
|
|
322
|
+
lent.open = 0;
|
|
209
323
|
try {
|
|
210
324
|
ctx.beginPath();
|
|
211
325
|
ctx.rect(x, y, w, h);
|
|
212
326
|
ctx.clip();
|
|
213
327
|
const opts = color ? { color } : undefined;
|
|
214
328
|
if (!box) {
|
|
215
|
-
view.draw(ctx, x, y, view.naturalWidth * scale, view.naturalHeight * scale, opts);
|
|
329
|
+
view.draw(lent.ctx, x, y, view.naturalWidth * scale * kx, view.naturalHeight * scale * ky, opts);
|
|
216
330
|
}
|
|
217
331
|
else {
|
|
218
332
|
const fit = (svgAttr(root, 'preserveAspectRatio') ?? '')
|
|
@@ -221,7 +335,7 @@ export class SvgDrawing {
|
|
|
221
335
|
if (fit[0] === 'defer')
|
|
222
336
|
fit.shift();
|
|
223
337
|
if (fit[0] === 'none') {
|
|
224
|
-
view.draw(ctx, x, y, w, h, opts);
|
|
338
|
+
view.draw(lent.ctx, x, y, w, h, opts);
|
|
225
339
|
}
|
|
226
340
|
else {
|
|
227
341
|
const align = ALIGN.exec(fit[0] ?? '') ?? ['xMidYMid', 'Mid', 'Mid'];
|
|
@@ -230,7 +344,7 @@ export class SvgDrawing {
|
|
|
230
344
|
const s = fit[1] === 'slice' ? Math.max(sx, sy) : Math.min(sx, sy);
|
|
231
345
|
const dw = box[2] * s;
|
|
232
346
|
const dh = box[3] * s;
|
|
233
|
-
view.draw(ctx, x + (w - dw) * AT[align[1]], y + (h - dh) * AT[align[2]], dw, dh, opts);
|
|
347
|
+
view.draw(lent.ctx, x + (w - dw) * AT[align[1]], y + (h - dh) * AT[align[2]], dw, dh, opts);
|
|
234
348
|
}
|
|
235
349
|
}
|
|
236
350
|
}
|
|
@@ -240,6 +354,12 @@ export class SvgDrawing {
|
|
|
240
354
|
this._failed = true;
|
|
241
355
|
}
|
|
242
356
|
finally {
|
|
357
|
+
// what the drawing saved and did not restore, before this one's own
|
|
358
|
+
// save — which the restore after it would otherwise have taken, and
|
|
359
|
+
// this one's clip been left on the window's context (`lend`)
|
|
360
|
+
for (; lent.open > 0; lent.open -= 1)
|
|
361
|
+
ctx.restore();
|
|
362
|
+
lent.open = outer;
|
|
243
363
|
ctx.restore();
|
|
244
364
|
}
|
|
245
365
|
}
|
|
@@ -268,7 +388,7 @@ export class SvgDrawing {
|
|
|
268
388
|
const made = `${scheme}|${scale}`;
|
|
269
389
|
let entry = this._cascades.get(made);
|
|
270
390
|
if (!entry) {
|
|
271
|
-
const cascade = new Cascade(sheets, imageLook(scheme, scale), w, h, scale, null, null, null, null, this._root);
|
|
391
|
+
const cascade = new Cascade(sheets, imageLook(scheme, scale), w, h, scale, null, null, null, null, this._root, this._target);
|
|
272
392
|
entry = { cascade, own: new Map() };
|
|
273
393
|
this._cascades.set(made, entry);
|
|
274
394
|
}
|
|
@@ -313,11 +433,13 @@ export class SvgDrawing {
|
|
|
313
433
|
paint = '', shapes = null) {
|
|
314
434
|
const root = this._root;
|
|
315
435
|
const count = root.children.length;
|
|
316
|
-
|
|
317
|
-
// more of the
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
436
|
+
// more of the document has arrived since a `<use>` found nothing, or
|
|
437
|
+
// more of what the tree was read from: the drawing, a group in it, or
|
|
438
|
+
// a symbol outside it that a chunk ended halfway through
|
|
439
|
+
const arrived = (this._missingAt !== undefined &&
|
|
440
|
+
lastNode(documentOf(root)) !== this._missingAt) ||
|
|
441
|
+
this._seenEnds.some(moved);
|
|
442
|
+
const grown = count !== this._seen || arrived;
|
|
321
443
|
if (grown || this._percent === null) {
|
|
322
444
|
this._percent = hasPercent(root);
|
|
323
445
|
this._uses = needsExpanding(root);
|
|
@@ -338,15 +460,20 @@ export class SvgDrawing {
|
|
|
338
460
|
}
|
|
339
461
|
try {
|
|
340
462
|
const view = this._view ?? new View(null);
|
|
341
|
-
const
|
|
463
|
+
const reached = { missing: false, outside: new Set() };
|
|
342
464
|
const tree = sized || shapes || root.name.includes(':')
|
|
343
|
-
? copyTree(root, sized ? [width, height] : null,
|
|
465
|
+
? copyTree(root, sized ? [width, height] : null, reached, shapes)
|
|
344
466
|
: root;
|
|
345
467
|
view.setSvgDom(paint ? withPaint(tree, paint) : tree);
|
|
346
|
-
this._missingAt = missing
|
|
468
|
+
this._missingAt = reached.missing
|
|
469
|
+
? lastNode(documentOf(root))
|
|
470
|
+
: undefined;
|
|
471
|
+
const ends = [endOf(root)];
|
|
472
|
+
for (const el of reached.outside)
|
|
473
|
+
ends.push(endOf(el));
|
|
347
474
|
this._view = view;
|
|
348
475
|
this._seen = count;
|
|
349
|
-
this.
|
|
476
|
+
this._seenEnds = ends;
|
|
350
477
|
this._seenViewport = viewport;
|
|
351
478
|
this._seenPaint = painted;
|
|
352
479
|
return view;
|
|
@@ -504,12 +631,6 @@ function hasPercent(root) {
|
|
|
504
631
|
};
|
|
505
632
|
return walk(root);
|
|
506
633
|
}
|
|
507
|
-
/** The element a `<use>` refers to by a fragment, its id: one in another
|
|
508
|
-
* document is none here, where nothing is fetched for a drawing. */
|
|
509
|
-
function useTarget(el) {
|
|
510
|
-
const href = el.attribs.href ?? el.attribs['xlink:href'] ?? '';
|
|
511
|
-
return href.length > 1 && href.startsWith('#') ? href.slice(1) : null;
|
|
512
|
-
}
|
|
513
634
|
/** The elements under a root by their ids, the first of each. */
|
|
514
635
|
function idsUnder(root) {
|
|
515
636
|
const ids = new Map();
|
|
@@ -530,10 +651,10 @@ function idsUnder(root) {
|
|
|
530
651
|
/**
|
|
531
652
|
* Whether a `<use>` under the root is one `SvgView` cannot draw as it
|
|
532
653
|
* stands: it looks a reference up among the root's own elements, and draws
|
|
533
|
-
* a `<symbol>` as its children where they are
|
|
534
|
-
* outside the root — an icon sprite's, a hidden
|
|
535
|
-
* top of the page — and one to a symbol with a
|
|
536
|
-
* drawn from a copy (`copyTree`).
|
|
654
|
+
* a `<symbol>` as its children where they are, with the `<use>`'s paint.
|
|
655
|
+
* So one to an element outside the root — an icon sprite's, a hidden
|
|
656
|
+
* `<svg>` of symbols at the top of the page — and one to a symbol with a
|
|
657
|
+
* `viewBox` or a paint of its own are drawn from a copy (`copyTree`).
|
|
537
658
|
*/
|
|
538
659
|
function needsExpanding(root) {
|
|
539
660
|
let ids = null;
|
|
@@ -543,13 +664,14 @@ function needsExpanding(root) {
|
|
|
543
664
|
continue;
|
|
544
665
|
const tag = child;
|
|
545
666
|
if (localName(tag.name) === 'use') {
|
|
546
|
-
const id =
|
|
667
|
+
const id = useHref(tag);
|
|
547
668
|
if (id !== null) {
|
|
548
669
|
ids ??= idsUnder(root);
|
|
549
670
|
const target = ids.get(id);
|
|
550
671
|
if (!target)
|
|
551
672
|
return true;
|
|
552
|
-
if (localName(target.name) === 'symbol' &&
|
|
673
|
+
if (localName(target.name) === 'symbol' &&
|
|
674
|
+
(viewBoxOf(target) || symbolOwn(target))) {
|
|
553
675
|
return true;
|
|
554
676
|
}
|
|
555
677
|
}
|
|
@@ -575,6 +697,17 @@ function lastNode(top) {
|
|
|
575
697
|
last = last.lastChild;
|
|
576
698
|
return last;
|
|
577
699
|
}
|
|
700
|
+
function endOf(top) {
|
|
701
|
+
const last = lastNode(top);
|
|
702
|
+
return { top, last, text: last?.type === 'text' ? last.data.length : -1 };
|
|
703
|
+
}
|
|
704
|
+
/** Whether more of a part has arrived since its `End` was taken. */
|
|
705
|
+
function moved(end) {
|
|
706
|
+
const last = lastNode(end.top);
|
|
707
|
+
if (last !== end.last)
|
|
708
|
+
return true;
|
|
709
|
+
return last?.type === 'text' && last.data.length !== end.text;
|
|
710
|
+
}
|
|
578
711
|
/** The first element of a document with an id, in its order. */
|
|
579
712
|
function elementById(top, id) {
|
|
580
713
|
const stack = [...top.children].reverse();
|
|
@@ -630,6 +763,43 @@ const USE_PLACEMENT = new Set([
|
|
|
630
763
|
'width',
|
|
631
764
|
'height',
|
|
632
765
|
]);
|
|
766
|
+
/** A `<symbol>`'s attributes that paint what is in it: its presentation
|
|
767
|
+
* attributes and its `style` (SVG 2, 6.3). Not where it goes, the viewport
|
|
768
|
+
* it makes or a name, which a symbol's copy is no group to have. */
|
|
769
|
+
const SYMBOL_PAINTS = new Set([
|
|
770
|
+
'style',
|
|
771
|
+
'fill',
|
|
772
|
+
'fill-opacity',
|
|
773
|
+
'fill-rule',
|
|
774
|
+
'stroke',
|
|
775
|
+
'stroke-width',
|
|
776
|
+
'stroke-linecap',
|
|
777
|
+
'stroke-linejoin',
|
|
778
|
+
'stroke-miterlimit',
|
|
779
|
+
'stroke-dasharray',
|
|
780
|
+
'stroke-dashoffset',
|
|
781
|
+
'stroke-opacity',
|
|
782
|
+
'color',
|
|
783
|
+
'opacity',
|
|
784
|
+
'visibility',
|
|
785
|
+
'display',
|
|
786
|
+
'font-family',
|
|
787
|
+
'font-size',
|
|
788
|
+
'font-style',
|
|
789
|
+
'font-weight',
|
|
790
|
+
'text-anchor',
|
|
791
|
+
]);
|
|
792
|
+
/** A `<symbol>`'s own attributes that paint what is in it, or null where
|
|
793
|
+
* it has none: Chrome draws a sprite's `<symbol fill="…">` in that fill,
|
|
794
|
+
* where `SvgView` draws a symbol's children alone. */
|
|
795
|
+
function symbolOwn(symbol) {
|
|
796
|
+
let kept = null;
|
|
797
|
+
for (const name in symbol.attribs) {
|
|
798
|
+
if (SYMBOL_PAINTS.has(name))
|
|
799
|
+
(kept ??= {})[name] = symbol.attribs[name];
|
|
800
|
+
}
|
|
801
|
+
return kept;
|
|
802
|
+
}
|
|
633
803
|
/**
|
|
634
804
|
* The tree `SvgView` reads, where the document's own will not do: with
|
|
635
805
|
* local names — `SvgView` knows `rect`, not `svg:rect`, and a prefix is
|
|
@@ -639,8 +809,9 @@ const USE_PLACEMENT = new Set([
|
|
|
639
809
|
* group of what it refers to (SVG 2, 5.5): the element, from wherever in
|
|
640
810
|
* the document it is, or a symbol's children, its `viewBox` fitted to the
|
|
641
811
|
* viewport the `<use>` gives it — its `width` and `height`, and all of the
|
|
642
|
-
* drawing's where it has none. `
|
|
643
|
-
* element the document does not have
|
|
812
|
+
* drawing's where it has none. `reached` is told where one refers to an
|
|
813
|
+
* element the document does not have, and of each element outside the root
|
|
814
|
+
* that one was drawn from.
|
|
644
815
|
*
|
|
645
816
|
* And with what the document's rules give each element (`shapes`), where
|
|
646
817
|
* `SvgView` reads an element's own: at the end of its `style`, over its
|
|
@@ -648,9 +819,12 @@ const USE_PLACEMENT = new Set([
|
|
|
648
819
|
* cascade that made them has weighed already. An element a rule gives
|
|
649
820
|
* `display: none` is left out, with what is in it, and a shape one hides
|
|
650
821
|
* — `visibility` is inherited, and a shape in a hidden group may be shown
|
|
651
|
-
* again — is left out alone.
|
|
822
|
+
* again — is left out alone. What a `<use>` draws has what the rules give
|
|
823
|
+
* its copy (`ShapeStyles.used`), which may not be what they give the
|
|
824
|
+
* element where it stands, so where they give anything, every `<use>` is
|
|
825
|
+
* drawn from a copy here.
|
|
652
826
|
*/
|
|
653
|
-
function copyTree(root, viewport,
|
|
827
|
+
function copyTree(root, viewport, reached, shapes = null) {
|
|
654
828
|
const colon = root.name.indexOf(':');
|
|
655
829
|
const prefix = colon < 0 ? null : `${root.name.slice(0, colon)}:`;
|
|
656
830
|
const strip = (name) => prefix && name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
@@ -658,29 +832,36 @@ function copyTree(root, viewport, missing, shapes = null) {
|
|
|
658
832
|
? Math.sqrt((viewport[0] ** 2 + viewport[1] ** 2) / 2)
|
|
659
833
|
: 0;
|
|
660
834
|
let ids = null;
|
|
661
|
-
const expand = (use, resolve, depth, hidden
|
|
662
|
-
|
|
835
|
+
const expand = (use, resolve, depth, hidden,
|
|
836
|
+
/** What the rules give the `<use>`. */
|
|
837
|
+
own) => {
|
|
838
|
+
const id = useHref(use);
|
|
663
839
|
if (id === null || depth >= USE_DEPTH)
|
|
664
840
|
return null;
|
|
665
841
|
ids ??= idsUnder(root);
|
|
666
842
|
const inside = ids.get(id);
|
|
667
843
|
const target = inside ?? elementById(documentOf(root), id);
|
|
668
844
|
if (!target) {
|
|
669
|
-
if (
|
|
670
|
-
missing
|
|
845
|
+
if (reached)
|
|
846
|
+
reached.missing = true;
|
|
671
847
|
return null;
|
|
672
848
|
}
|
|
849
|
+
if (!inside)
|
|
850
|
+
reached?.outside.add(target);
|
|
673
851
|
const symbol = localName(target.name) === 'symbol';
|
|
674
852
|
const box = symbol ? viewBoxOf(target) : null;
|
|
675
|
-
|
|
676
|
-
|
|
853
|
+
const kept = symbol ? symbolOwn(target) : null;
|
|
854
|
+
// one of the root's own that `SvgView` draws as it is: with no
|
|
855
|
+
// viewport to fit, no paint of a symbol's own, and no rule's styles
|
|
856
|
+
if (inside && !box && !kept && !shapes)
|
|
677
857
|
return null;
|
|
858
|
+
// what the rules give the copy, element by element
|
|
859
|
+
const styles = shapes?.used?.get(use) ?? null;
|
|
678
860
|
const attribs = {};
|
|
679
861
|
for (const name in use.attribs) {
|
|
680
862
|
if (!USE_PLACEMENT.has(name))
|
|
681
863
|
attribs[name] = use.attribs[name];
|
|
682
864
|
}
|
|
683
|
-
const own = shapes?.of.get(use);
|
|
684
865
|
if (own)
|
|
685
866
|
restyle(attribs, own);
|
|
686
867
|
const x = parseFloat(use.attribs.x ?? '') || 0;
|
|
@@ -703,29 +884,47 @@ function copyTree(root, viewport, missing, shapes = null) {
|
|
|
703
884
|
// a length in a symbol with a `viewBox` is of that, and left as it is
|
|
704
885
|
const within = resolve && !box;
|
|
705
886
|
const children = [];
|
|
706
|
-
if (symbol)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
children
|
|
887
|
+
if (symbol) {
|
|
888
|
+
// The symbol is the top of the copy, and paints what is in it: its
|
|
889
|
+
// attributes, and what the rules give it there. `SvgView` draws a
|
|
890
|
+
// symbol's children alone, so they go in a group that is it. Where
|
|
891
|
+
// it stands a symbol is drawn by nobody, so `display: none` takes
|
|
892
|
+
// nothing from it there (`UNDRAWN`); at the top of a copy it takes
|
|
893
|
+
// the copy, as Chrome has it.
|
|
894
|
+
const mine = styles?.get(target);
|
|
895
|
+
const inner = hiddenIn(mine, hidden);
|
|
896
|
+
if (mine?.display !== 'none') {
|
|
897
|
+
copyInto(children, target, within, depth + 1, inner, styles);
|
|
898
|
+
}
|
|
899
|
+
if (kept || mine) {
|
|
900
|
+
const paints = { ...kept };
|
|
901
|
+
if (mine)
|
|
902
|
+
restyle(paints, mine);
|
|
903
|
+
const group = new Element('g', paints, children.splice(0));
|
|
904
|
+
children.push(group);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
else if (shown(target, hidden, styles)) {
|
|
908
|
+
children.push(copy(target, within, depth + 1, hidden, styles));
|
|
710
909
|
}
|
|
711
910
|
return new Element('g', attribs, children);
|
|
712
911
|
};
|
|
713
912
|
/** Whether an element is drawn at all, in a group that is hidden or
|
|
714
913
|
* not: one no rule gives `display: none`, and no shape a rule hides. */
|
|
715
|
-
const shown = (el, hidden) => {
|
|
716
|
-
if (!
|
|
914
|
+
const shown = (el, hidden, styles) => {
|
|
915
|
+
if (!styles)
|
|
717
916
|
return true;
|
|
718
|
-
const own =
|
|
917
|
+
const own = styles.get(el);
|
|
719
918
|
const name = localName(el.name);
|
|
720
919
|
if (own?.display === 'none' && !UNDRAWN.has(name))
|
|
721
920
|
return false;
|
|
722
921
|
return !(hiddenIn(own, hidden) && HIDEABLE.has(name));
|
|
723
922
|
};
|
|
724
|
-
const copyInto = (children, el, resolve, depth, hidden) => {
|
|
923
|
+
const copyInto = (children, el, resolve, depth, hidden, styles) => {
|
|
725
924
|
for (const child of el.children) {
|
|
726
925
|
if (child.type === 'tag') {
|
|
727
|
-
if (shown(child, hidden)) {
|
|
728
|
-
children.push(copy(child, resolve, depth, hidden));
|
|
926
|
+
if (shown(child, hidden, styles)) {
|
|
927
|
+
children.push(copy(child, resolve, depth, hidden, styles));
|
|
729
928
|
}
|
|
730
929
|
}
|
|
731
930
|
else if (child.type === 'text')
|
|
@@ -734,11 +933,14 @@ function copyTree(root, viewport, missing, shapes = null) {
|
|
|
734
933
|
};
|
|
735
934
|
const copy = (el, resolve, depth = 0,
|
|
736
935
|
/** Whether the element inherits a `visibility` of `hidden`. */
|
|
737
|
-
hidden = false
|
|
738
|
-
|
|
936
|
+
hidden = false,
|
|
937
|
+
/** What the rules give the elements of the tree it is in: the
|
|
938
|
+
* drawing's, or a `<use>`'s copy's. */
|
|
939
|
+
styles = shapes?.of ?? null) => {
|
|
940
|
+
const own = styles?.get(el);
|
|
739
941
|
hidden = hiddenIn(own, hidden);
|
|
740
942
|
if (localName(el.name) === 'use') {
|
|
741
|
-
const group = expand(el, resolve, depth, hidden);
|
|
943
|
+
const group = expand(el, resolve, depth, hidden, own);
|
|
742
944
|
if (group)
|
|
743
945
|
return group;
|
|
744
946
|
}
|
|
@@ -760,7 +962,7 @@ function copyTree(root, viewport, missing, shapes = null) {
|
|
|
760
962
|
}
|
|
761
963
|
}
|
|
762
964
|
const children = [];
|
|
763
|
-
copyInto(children, el, here, depth, hidden);
|
|
965
|
+
copyInto(children, el, here, depth, hidden, styles);
|
|
764
966
|
return new Element(strip(el.name), attribs, children);
|
|
765
967
|
};
|
|
766
968
|
return copy(root, true);
|
|
@@ -839,7 +1041,7 @@ function restyle(attribs, own) {
|
|
|
839
1041
|
* which is how a PNG or a JPEG goes on to the image decoder. Nothing names
|
|
840
1042
|
* the type, so it is sniffed: markup, with an `<svg>` root.
|
|
841
1043
|
*/
|
|
842
|
-
export function svgFromBytes(bytes) {
|
|
1044
|
+
export function svgFromBytes(bytes, fragment = '') {
|
|
843
1045
|
let i = bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf ? 3 : 0;
|
|
844
1046
|
while (bytes[i] === 0x20 ||
|
|
845
1047
|
bytes[i] === 0x09 ||
|
|
@@ -855,7 +1057,10 @@ export function svgFromBytes(bytes) {
|
|
|
855
1057
|
try {
|
|
856
1058
|
const doc = parseDocument(text, { xmlMode: true });
|
|
857
1059
|
const root = findRoot(doc.children);
|
|
858
|
-
|
|
1060
|
+
if (!root)
|
|
1061
|
+
return null;
|
|
1062
|
+
const target = fragment ? elementById(doc, fragment) : null;
|
|
1063
|
+
return new SvgDrawing(root, svgIntrinsics(root), true, target);
|
|
859
1064
|
}
|
|
860
1065
|
catch {
|
|
861
1066
|
return null;
|