@react-x11/components 0.14.0 → 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 +209 -23
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +873 -175
- 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 +62 -0
- package/dist/html/css/shapes.d.ts.map +1 -0
- package/dist/html/css/shapes.js +214 -0
- package/dist/html/css/shapes.js.map +1 -0
- 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 +75 -27
- 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 +38 -7
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +160 -50
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +29 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +159 -20
- 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 +69 -19
- 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 +1648 -225
- 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 +907 -323
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +36 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +174 -37
- 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 +769 -204
- 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 +50 -17
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +501 -83
- 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 +987 -176
- 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 +273 -0
- package/src/html/css/style.ts +123 -30
- 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 +169 -56
- package/src/html/layout/boxes.ts +198 -24
- package/src/html/layout/css-grid.ts +89 -22
- package/src/html/layout/flex.ts +1863 -219
- package/src/html/layout/inline.ts +27 -7
- package/src/html/layout/table.ts +989 -333
- package/src/html/node.ts +204 -37
- package/src/html/paint.ts +999 -252
- package/src/html/resources.ts +13 -1
- package/src/html/svg.ts +600 -78
- package/src/html/widgets.ts +61 -4
package/dist/html/svg.js
CHANGED
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
import { parseDocument } from 'htmlparser2';
|
|
16
16
|
import { Element, Text } from 'domhandler';
|
|
17
17
|
import * as ntk from 'react-x11/ntk';
|
|
18
|
-
import { isSvgRoot } from './dom.js';
|
|
18
|
+
import { isSvgRoot, rawTextOf } from './dom.js';
|
|
19
|
+
import { Cascade } from './css/cascade.js';
|
|
20
|
+
import { parseMediaQuery, parseStylesheet } from './css/parse.js';
|
|
21
|
+
import { useHref } from './css/shapes.js';
|
|
19
22
|
import { inkColor, isTransparent, parseColor } from './css/values.js';
|
|
20
23
|
export { isSvgRoot };
|
|
21
24
|
function svgAttr(el, name) {
|
|
@@ -56,21 +59,6 @@ function absoluteLength(value, fontSize) {
|
|
|
56
59
|
return null;
|
|
57
60
|
return px >= 0 ? px : null;
|
|
58
61
|
}
|
|
59
|
-
/**
|
|
60
|
-
* The `width`/`height` of an inline `<svg>` as CSS: they are presentation
|
|
61
|
-
* attributes for the properties of the same names (SVG 2, 5.1.1), so
|
|
62
|
-
* `height="50%"` is a percentage of the containing block like a style's.
|
|
63
|
-
* A bare number is pixels; null for what CSS would not parse.
|
|
64
|
-
*/
|
|
65
|
-
export function svgSizeHint(value) {
|
|
66
|
-
const v = value.trim();
|
|
67
|
-
if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?%$/i.test(v))
|
|
68
|
-
return v;
|
|
69
|
-
const m = LENGTH.exec(v);
|
|
70
|
-
if (!m)
|
|
71
|
-
return null;
|
|
72
|
-
return m[2] ? v : `${parseFloat(m[1])}px`;
|
|
73
|
-
}
|
|
74
62
|
function viewBoxParts(el) {
|
|
75
63
|
const parts = (svgAttr(el, 'viewBox') ?? '')
|
|
76
64
|
.trim()
|
|
@@ -115,10 +103,93 @@ export function svgIntrinsics(el, fontSize = 16) {
|
|
|
115
103
|
}
|
|
116
104
|
return { width, height, ratio };
|
|
117
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
|
+
}
|
|
118
133
|
function svgViewClass() {
|
|
119
134
|
const ctor = ntk.SvgView;
|
|
120
135
|
return typeof ctor === 'function' ? ctor : null;
|
|
121
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
|
+
}
|
|
122
193
|
const ALIGN = /^x(Min|Mid|Max)Y(Min|Mid|Max)$/;
|
|
123
194
|
const AT = { Min: 0, Mid: 0.5, Max: 1 };
|
|
124
195
|
/**
|
|
@@ -130,14 +201,17 @@ export class SvgDrawing {
|
|
|
130
201
|
intrinsics;
|
|
131
202
|
_root;
|
|
132
203
|
_view = null;
|
|
133
|
-
/** What the view was last handed the tree for: the root's child count
|
|
134
|
-
* and
|
|
135
|
-
*
|
|
136
|
-
*
|
|
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. */
|
|
137
210
|
_seen = -1;
|
|
138
|
-
|
|
211
|
+
_seenEnds = [];
|
|
139
212
|
_seenViewport = '';
|
|
140
|
-
/** The `fill` and `stroke` the view's tree was last given its root's
|
|
213
|
+
/** The `fill` and `stroke` the view's tree was last given its root's,
|
|
214
|
+
* and what the document's rules gave the elements in it. */
|
|
141
215
|
_seenPaint = '';
|
|
142
216
|
/** Whether a length in the tree is a percentage of the viewport; null
|
|
143
217
|
* until the tree is first read. */
|
|
@@ -155,10 +229,21 @@ export class SvgDrawing {
|
|
|
155
229
|
/** The background an image's root gives its canvas; undefined until it
|
|
156
230
|
* is first read. */
|
|
157
231
|
_canvas = undefined;
|
|
158
|
-
|
|
232
|
+
/** An SVG image's own style sheets, its `<style>` elements': read at its
|
|
233
|
+
* first draw, and none for most images. */
|
|
234
|
+
_sheets = null;
|
|
235
|
+
/** A cascade over them for each colour scheme and scale the image is
|
|
236
|
+
* drawn at, and what each gave it: by the viewport it was asked at where
|
|
237
|
+
* a sheet reads the viewport, and under one key where none does. */
|
|
238
|
+
_cascades = new Map();
|
|
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) {
|
|
159
243
|
this._root = root;
|
|
160
244
|
this.intrinsics = intrinsics;
|
|
161
245
|
this._standalone = standalone;
|
|
246
|
+
this._target = target;
|
|
162
247
|
}
|
|
163
248
|
/**
|
|
164
249
|
* Draw into a rectangle — the box's content box, in device pixels —
|
|
@@ -168,9 +253,10 @@ export class SvgDrawing {
|
|
|
168
253
|
* and `stroke` the root's own where the document's styles set them (SVG
|
|
169
254
|
* 2, 13.2): they are properties, which a style sheet's rule sets over
|
|
170
255
|
* the presentation attribute, and what is in the drawing inherits them
|
|
171
|
-
* from its root.
|
|
256
|
+
* from its root. `shapes` is what the rules give the elements inside it,
|
|
257
|
+
* the same way, where they give any.
|
|
172
258
|
*/
|
|
173
|
-
draw(ctx, x, y, w, h, scale, color, fill = null, stroke = null) {
|
|
259
|
+
draw(ctx, x, y, w, h, scale, color, fill = null, stroke = null, shapes = null) {
|
|
174
260
|
if (this._failed || !(w > 0 && h > 0))
|
|
175
261
|
return;
|
|
176
262
|
// the mock backend has no path API, and SvgView draws paths
|
|
@@ -202,21 +288,45 @@ export class SvgDrawing {
|
|
|
202
288
|
}
|
|
203
289
|
const box = viewBoxOf(root);
|
|
204
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
|
+
}
|
|
205
311
|
const paint = (fill === null ? '' : `fill:${fill};`) +
|
|
206
312
|
(stroke === null ? '' : `stroke:${stroke};`);
|
|
207
313
|
const view = box
|
|
208
|
-
? this._viewFor(box[2], box[3], paint)
|
|
209
|
-
: this._viewFor(
|
|
314
|
+
? this._viewFor(box[2], box[3], paint, shapes)
|
|
315
|
+
: this._viewFor(vw, vh, paint, shapes);
|
|
210
316
|
if (!view)
|
|
211
317
|
return;
|
|
212
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;
|
|
213
323
|
try {
|
|
214
324
|
ctx.beginPath();
|
|
215
325
|
ctx.rect(x, y, w, h);
|
|
216
326
|
ctx.clip();
|
|
217
327
|
const opts = color ? { color } : undefined;
|
|
218
328
|
if (!box) {
|
|
219
|
-
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);
|
|
220
330
|
}
|
|
221
331
|
else {
|
|
222
332
|
const fit = (svgAttr(root, 'preserveAspectRatio') ?? '')
|
|
@@ -225,7 +335,7 @@ export class SvgDrawing {
|
|
|
225
335
|
if (fit[0] === 'defer')
|
|
226
336
|
fit.shift();
|
|
227
337
|
if (fit[0] === 'none') {
|
|
228
|
-
view.draw(ctx, x, y, w, h, opts);
|
|
338
|
+
view.draw(lent.ctx, x, y, w, h, opts);
|
|
229
339
|
}
|
|
230
340
|
else {
|
|
231
341
|
const align = ALIGN.exec(fit[0] ?? '') ?? ['xMidYMid', 'Mid', 'Mid'];
|
|
@@ -234,7 +344,7 @@ export class SvgDrawing {
|
|
|
234
344
|
const s = fit[1] === 'slice' ? Math.max(sx, sy) : Math.min(sx, sy);
|
|
235
345
|
const dw = box[2] * s;
|
|
236
346
|
const dh = box[3] * s;
|
|
237
|
-
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);
|
|
238
348
|
}
|
|
239
349
|
}
|
|
240
350
|
}
|
|
@@ -244,29 +354,103 @@ export class SvgDrawing {
|
|
|
244
354
|
this._failed = true;
|
|
245
355
|
}
|
|
246
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;
|
|
247
363
|
ctx.restore();
|
|
248
364
|
}
|
|
249
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Draw an SVG image — an `<img>`'s, a background's, a list marker's — as
|
|
368
|
+
* `draw` does, painted as its own style sheets say. An image is a
|
|
369
|
+
* document of its own: no rule of the page's reaches into it, and its
|
|
370
|
+
* `color` is not the page's. What its `<style>` elements say is given
|
|
371
|
+
* its elements as the page's rules are given a drawing inline, and its
|
|
372
|
+
* root its `color`, `fill` and `stroke`, which an inline root has from
|
|
373
|
+
* its box. `scheme` is the colour scheme of the element that embeds it:
|
|
374
|
+
* what `prefers-color-scheme` answers inside it, as Chrome has it.
|
|
375
|
+
*/
|
|
376
|
+
drawImage(ctx, x, y, w, h, scale, scheme = 'light') {
|
|
377
|
+
if (this._failed || !(w > 0 && h > 0))
|
|
378
|
+
return;
|
|
379
|
+
const own = this._ownPaint(w, h, scale, scheme);
|
|
380
|
+
this.draw(ctx, x, y, w, h, scale, own?.color, own?.fill ?? null, own?.stroke ?? null, own?.shapes ?? null);
|
|
381
|
+
}
|
|
382
|
+
/** What an image's own style sheets give it, drawn `w` by `h` device
|
|
383
|
+
* pixels, or null where it has none. */
|
|
384
|
+
_ownPaint(w, h, scale, scheme) {
|
|
385
|
+
const sheets = (this._sheets ??= ownSheets(this._root));
|
|
386
|
+
if (!sheets.length)
|
|
387
|
+
return null;
|
|
388
|
+
const made = `${scheme}|${scale}`;
|
|
389
|
+
let entry = this._cascades.get(made);
|
|
390
|
+
if (!entry) {
|
|
391
|
+
const cascade = new Cascade(sheets, imageLook(scheme, scale), w, h, scale, null, null, null, null, this._root, this._target);
|
|
392
|
+
entry = { cascade, own: new Map() };
|
|
393
|
+
this._cascades.set(made, entry);
|
|
394
|
+
}
|
|
395
|
+
const { cascade, own } = entry;
|
|
396
|
+
// An image's viewport is the rectangle it is drawn in, which is what a
|
|
397
|
+
// `@media (min-width)` in it, or a `vw`, is of: the answer is kept by
|
|
398
|
+
// that size only where a sheet asks.
|
|
399
|
+
const sized = cascade.breakpoints.length > 0 ||
|
|
400
|
+
cascade.readsViewportWidth ||
|
|
401
|
+
cascade.readsViewportHeight;
|
|
402
|
+
const key = sized ? `${w}x${h}` : '';
|
|
403
|
+
const kept = own.get(key);
|
|
404
|
+
if (kept !== undefined)
|
|
405
|
+
return kept;
|
|
406
|
+
cascade.viewportWidth = w;
|
|
407
|
+
cascade.viewportHeight = h;
|
|
408
|
+
let paint = null;
|
|
409
|
+
try {
|
|
410
|
+
const root = this._root;
|
|
411
|
+
const style = cascade.styleFor(root, cascade.initial, false);
|
|
412
|
+
const ink = (value) => value === null || value === 'currentColor' || value === 'none'
|
|
413
|
+
? value
|
|
414
|
+
: inkColor(value, style.color);
|
|
415
|
+
paint = {
|
|
416
|
+
color: style.color,
|
|
417
|
+
fill: ink(style.fill),
|
|
418
|
+
stroke: ink(style.stroke),
|
|
419
|
+
shapes: cascade.shapeStyles(root, style),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
// a sheet this cannot read leaves the image as its attributes draw it
|
|
424
|
+
}
|
|
425
|
+
// a background drawn at many sizes keeps a few of them
|
|
426
|
+
if (own.size >= 8)
|
|
427
|
+
own.clear();
|
|
428
|
+
own.set(key, paint);
|
|
429
|
+
return paint;
|
|
430
|
+
}
|
|
250
431
|
_viewFor(width, height,
|
|
251
432
|
/** The root's `fill` and `stroke` as declarations, or none. */
|
|
252
|
-
paint = '') {
|
|
433
|
+
paint = '', shapes = null) {
|
|
253
434
|
const root = this._root;
|
|
254
435
|
const count = root.children.length;
|
|
255
|
-
|
|
256
|
-
// more of the
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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;
|
|
260
443
|
if (grown || this._percent === null) {
|
|
261
444
|
this._percent = hasPercent(root);
|
|
262
445
|
this._uses = needsExpanding(root);
|
|
263
446
|
}
|
|
264
447
|
const sized = this._percent || this._uses;
|
|
265
448
|
const viewport = sized ? `${width}x${height}` : '';
|
|
449
|
+
const painted = shapes ? `${paint}|${shapes.key}` : paint;
|
|
266
450
|
if (this._view &&
|
|
267
451
|
!grown &&
|
|
268
452
|
viewport === this._seenViewport &&
|
|
269
|
-
|
|
453
|
+
painted === this._seenPaint) {
|
|
270
454
|
return this._view;
|
|
271
455
|
}
|
|
272
456
|
const View = svgViewClass();
|
|
@@ -276,17 +460,22 @@ export class SvgDrawing {
|
|
|
276
460
|
}
|
|
277
461
|
try {
|
|
278
462
|
const view = this._view ?? new View(null);
|
|
279
|
-
const
|
|
280
|
-
const tree = sized || root.name.includes(':')
|
|
281
|
-
? copyTree(root, sized ? [width, height] : null,
|
|
463
|
+
const reached = { missing: false, outside: new Set() };
|
|
464
|
+
const tree = sized || shapes || root.name.includes(':')
|
|
465
|
+
? copyTree(root, sized ? [width, height] : null, reached, shapes)
|
|
282
466
|
: root;
|
|
283
|
-
view.setSvgDom(paint ?
|
|
284
|
-
this._missingAt = missing
|
|
467
|
+
view.setSvgDom(paint ? withPaint(tree, paint) : tree);
|
|
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));
|
|
285
474
|
this._view = view;
|
|
286
475
|
this._seen = count;
|
|
287
|
-
this.
|
|
476
|
+
this._seenEnds = ends;
|
|
288
477
|
this._seenViewport = viewport;
|
|
289
|
-
this._seenPaint =
|
|
478
|
+
this._seenPaint = painted;
|
|
290
479
|
return view;
|
|
291
480
|
}
|
|
292
481
|
catch {
|
|
@@ -301,7 +490,7 @@ export class SvgDrawing {
|
|
|
301
490
|
* its `style` — where `SvgView` reads a root's paint, over its presentation
|
|
302
491
|
* attributes, for what is under it to inherit.
|
|
303
492
|
*/
|
|
304
|
-
function
|
|
493
|
+
function withPaint(root, paint) {
|
|
305
494
|
const style = root.attribs.style;
|
|
306
495
|
return new Element(root.name, { ...root.attribs, style: style ? `${style};${paint}` : paint }, root.children);
|
|
307
496
|
}
|
|
@@ -328,6 +517,59 @@ function canvasBackground(root) {
|
|
|
328
517
|
}
|
|
329
518
|
return found;
|
|
330
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* The style sheets an SVG image's `<style>` elements hold, in order. One
|
|
522
|
+
* that names a type other than CSS's holds none (SVG 2, 6.3), and one with
|
|
523
|
+
* a `media` query holds its rules under it. Its text is its text and CDATA
|
|
524
|
+
* sections, as an XML parse leaves them, and not its comments.
|
|
525
|
+
*/
|
|
526
|
+
function ownSheets(root) {
|
|
527
|
+
const sheets = [];
|
|
528
|
+
const layers = new Map();
|
|
529
|
+
let order = 0;
|
|
530
|
+
const stack = [root];
|
|
531
|
+
for (let el = stack.pop(); el; el = stack.pop()) {
|
|
532
|
+
for (let i = el.children.length - 1; i >= 0; i -= 1) {
|
|
533
|
+
const child = el.children[i];
|
|
534
|
+
// an XML parse gives a `<style>` a type of its own, as HTML's does
|
|
535
|
+
if (child.type === 'tag' || child.type === 'style') {
|
|
536
|
+
stack.push(child);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (el === root || localName(el.name) !== 'style')
|
|
540
|
+
continue;
|
|
541
|
+
const type = (el.attribs.type ?? '').trim().toLowerCase();
|
|
542
|
+
if (type && type !== 'text/css')
|
|
543
|
+
continue;
|
|
544
|
+
const media = el.attribs.media?.trim();
|
|
545
|
+
const sheet = parseStylesheet(rawTextOf(el), order, layers, null, media ? [parseMediaQuery(media)] : null);
|
|
546
|
+
order += sheet.rules.length + 1;
|
|
547
|
+
sheets.push(sheet);
|
|
548
|
+
}
|
|
549
|
+
return sheets;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* The look an SVG image's own document starts from: CSS's initial values,
|
|
553
|
+
* black at `medium`, since nothing of the page's is inherited into an
|
|
554
|
+
* image — only the colour scheme, which its `prefers-color-scheme` answers.
|
|
555
|
+
*/
|
|
556
|
+
function imageLook(scheme, scale) {
|
|
557
|
+
return {
|
|
558
|
+
color: '#000000',
|
|
559
|
+
fontFamily: 'sans-serif',
|
|
560
|
+
fontSize: 16 * scale,
|
|
561
|
+
monoFamily: 'monospace',
|
|
562
|
+
linkColor: '#0000ee',
|
|
563
|
+
borderColor: '#000000',
|
|
564
|
+
mutedColor: '#808080',
|
|
565
|
+
background: 'transparent',
|
|
566
|
+
colorScheme: scheme,
|
|
567
|
+
surface: '#ffffff',
|
|
568
|
+
controlPadY: 0,
|
|
569
|
+
controlBorder: 0,
|
|
570
|
+
controlRadius: 0,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
331
573
|
/** Drawings of inline `<svg>` elements, per element: the element is the
|
|
332
574
|
* document's, and outlives the box trees built over it. */
|
|
333
575
|
const INLINE = new WeakMap();
|
|
@@ -389,12 +631,6 @@ function hasPercent(root) {
|
|
|
389
631
|
};
|
|
390
632
|
return walk(root);
|
|
391
633
|
}
|
|
392
|
-
/** The element a `<use>` refers to by a fragment, its id: one in another
|
|
393
|
-
* document is none here, where nothing is fetched for a drawing. */
|
|
394
|
-
function useTarget(el) {
|
|
395
|
-
const href = el.attribs.href ?? el.attribs['xlink:href'] ?? '';
|
|
396
|
-
return href.length > 1 && href.startsWith('#') ? href.slice(1) : null;
|
|
397
|
-
}
|
|
398
634
|
/** The elements under a root by their ids, the first of each. */
|
|
399
635
|
function idsUnder(root) {
|
|
400
636
|
const ids = new Map();
|
|
@@ -415,10 +651,10 @@ function idsUnder(root) {
|
|
|
415
651
|
/**
|
|
416
652
|
* Whether a `<use>` under the root is one `SvgView` cannot draw as it
|
|
417
653
|
* stands: it looks a reference up among the root's own elements, and draws
|
|
418
|
-
* a `<symbol>` as its children where they are
|
|
419
|
-
* outside the root — an icon sprite's, a hidden
|
|
420
|
-
* top of the page — and one to a symbol with a
|
|
421
|
-
* 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`).
|
|
422
658
|
*/
|
|
423
659
|
function needsExpanding(root) {
|
|
424
660
|
let ids = null;
|
|
@@ -428,13 +664,14 @@ function needsExpanding(root) {
|
|
|
428
664
|
continue;
|
|
429
665
|
const tag = child;
|
|
430
666
|
if (localName(tag.name) === 'use') {
|
|
431
|
-
const id =
|
|
667
|
+
const id = useHref(tag);
|
|
432
668
|
if (id !== null) {
|
|
433
669
|
ids ??= idsUnder(root);
|
|
434
670
|
const target = ids.get(id);
|
|
435
671
|
if (!target)
|
|
436
672
|
return true;
|
|
437
|
-
if (localName(target.name) === 'symbol' &&
|
|
673
|
+
if (localName(target.name) === 'symbol' &&
|
|
674
|
+
(viewBoxOf(target) || symbolOwn(target))) {
|
|
438
675
|
return true;
|
|
439
676
|
}
|
|
440
677
|
}
|
|
@@ -460,6 +697,17 @@ function lastNode(top) {
|
|
|
460
697
|
last = last.lastChild;
|
|
461
698
|
return last;
|
|
462
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
|
+
}
|
|
463
711
|
/** The first element of a document with an id, in its order. */
|
|
464
712
|
function elementById(top, id) {
|
|
465
713
|
const stack = [...top.children].reverse();
|
|
@@ -515,6 +763,43 @@ const USE_PLACEMENT = new Set([
|
|
|
515
763
|
'width',
|
|
516
764
|
'height',
|
|
517
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
|
+
}
|
|
518
803
|
/**
|
|
519
804
|
* The tree `SvgView` reads, where the document's own will not do: with
|
|
520
805
|
* local names — `SvgView` knows `rect`, not `svg:rect`, and a prefix is
|
|
@@ -524,10 +809,22 @@ const USE_PLACEMENT = new Set([
|
|
|
524
809
|
* group of what it refers to (SVG 2, 5.5): the element, from wherever in
|
|
525
810
|
* the document it is, or a symbol's children, its `viewBox` fitted to the
|
|
526
811
|
* viewport the `<use>` gives it — its `width` and `height`, and all of the
|
|
527
|
-
* drawing's where it has none. `
|
|
528
|
-
* 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.
|
|
815
|
+
*
|
|
816
|
+
* And with what the document's rules give each element (`shapes`), where
|
|
817
|
+
* `SvgView` reads an element's own: at the end of its `style`, over its
|
|
818
|
+
* presentation attributes and over what that attribute says, which the
|
|
819
|
+
* cascade that made them has weighed already. An element a rule gives
|
|
820
|
+
* `display: none` is left out, with what is in it, and a shape one hides
|
|
821
|
+
* — `visibility` is inherited, and a shape in a hidden group may be shown
|
|
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.
|
|
529
826
|
*/
|
|
530
|
-
function copyTree(root, viewport,
|
|
827
|
+
function copyTree(root, viewport, reached, shapes = null) {
|
|
531
828
|
const colon = root.name.indexOf(':');
|
|
532
829
|
const prefix = colon < 0 ? null : `${root.name.slice(0, colon)}:`;
|
|
533
830
|
const strip = (name) => prefix && name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
@@ -535,28 +832,38 @@ function copyTree(root, viewport, missing) {
|
|
|
535
832
|
? Math.sqrt((viewport[0] ** 2 + viewport[1] ** 2) / 2)
|
|
536
833
|
: 0;
|
|
537
834
|
let ids = null;
|
|
538
|
-
const expand = (use, resolve, depth
|
|
539
|
-
|
|
835
|
+
const expand = (use, resolve, depth, hidden,
|
|
836
|
+
/** What the rules give the `<use>`. */
|
|
837
|
+
own) => {
|
|
838
|
+
const id = useHref(use);
|
|
540
839
|
if (id === null || depth >= USE_DEPTH)
|
|
541
840
|
return null;
|
|
542
841
|
ids ??= idsUnder(root);
|
|
543
842
|
const inside = ids.get(id);
|
|
544
843
|
const target = inside ?? elementById(documentOf(root), id);
|
|
545
844
|
if (!target) {
|
|
546
|
-
if (
|
|
547
|
-
missing
|
|
845
|
+
if (reached)
|
|
846
|
+
reached.missing = true;
|
|
548
847
|
return null;
|
|
549
848
|
}
|
|
849
|
+
if (!inside)
|
|
850
|
+
reached?.outside.add(target);
|
|
550
851
|
const symbol = localName(target.name) === 'symbol';
|
|
551
852
|
const box = symbol ? viewBoxOf(target) : null;
|
|
552
|
-
|
|
553
|
-
|
|
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)
|
|
554
857
|
return null;
|
|
858
|
+
// what the rules give the copy, element by element
|
|
859
|
+
const styles = shapes?.used?.get(use) ?? null;
|
|
555
860
|
const attribs = {};
|
|
556
861
|
for (const name in use.attribs) {
|
|
557
862
|
if (!USE_PLACEMENT.has(name))
|
|
558
863
|
attribs[name] = use.attribs[name];
|
|
559
864
|
}
|
|
865
|
+
if (own)
|
|
866
|
+
restyle(attribs, own);
|
|
560
867
|
const x = parseFloat(use.attribs.x ?? '') || 0;
|
|
561
868
|
const y = parseFloat(use.attribs.y ?? '') || 0;
|
|
562
869
|
let placed = '';
|
|
@@ -578,24 +885,69 @@ function copyTree(root, viewport, missing) {
|
|
|
578
885
|
const within = resolve && !box;
|
|
579
886
|
const children = [];
|
|
580
887
|
if (symbol) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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);
|
|
585
905
|
}
|
|
586
906
|
}
|
|
587
|
-
else
|
|
588
|
-
children.push(copy(target, within, depth + 1));
|
|
907
|
+
else if (shown(target, hidden, styles)) {
|
|
908
|
+
children.push(copy(target, within, depth + 1, hidden, styles));
|
|
909
|
+
}
|
|
589
910
|
return new Element('g', attribs, children);
|
|
590
911
|
};
|
|
591
|
-
|
|
912
|
+
/** Whether an element is drawn at all, in a group that is hidden or
|
|
913
|
+
* not: one no rule gives `display: none`, and no shape a rule hides. */
|
|
914
|
+
const shown = (el, hidden, styles) => {
|
|
915
|
+
if (!styles)
|
|
916
|
+
return true;
|
|
917
|
+
const own = styles.get(el);
|
|
918
|
+
const name = localName(el.name);
|
|
919
|
+
if (own?.display === 'none' && !UNDRAWN.has(name))
|
|
920
|
+
return false;
|
|
921
|
+
return !(hiddenIn(own, hidden) && HIDEABLE.has(name));
|
|
922
|
+
};
|
|
923
|
+
const copyInto = (children, el, resolve, depth, hidden, styles) => {
|
|
924
|
+
for (const child of el.children) {
|
|
925
|
+
if (child.type === 'tag') {
|
|
926
|
+
if (shown(child, hidden, styles)) {
|
|
927
|
+
children.push(copy(child, resolve, depth, hidden, styles));
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
else if (child.type === 'text')
|
|
931
|
+
children.push(new Text(child.data));
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
const copy = (el, resolve, depth = 0,
|
|
935
|
+
/** Whether the element inherits a `visibility` of `hidden`. */
|
|
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);
|
|
941
|
+
hidden = hiddenIn(own, hidden);
|
|
592
942
|
if (localName(el.name) === 'use') {
|
|
593
|
-
const group = expand(el, resolve, depth);
|
|
943
|
+
const group = expand(el, resolve, depth, hidden, own);
|
|
594
944
|
if (group)
|
|
595
945
|
return group;
|
|
596
946
|
}
|
|
597
947
|
const here = resolve && !OWN_UNITS.has(localName(el.name));
|
|
598
948
|
const attribs = { ...el.attribs };
|
|
949
|
+
if (own)
|
|
950
|
+
restyle(attribs, own);
|
|
599
951
|
if (here && viewport && el !== root) {
|
|
600
952
|
for (const name in attribs) {
|
|
601
953
|
const axis = PERCENT_OF[name];
|
|
@@ -610,23 +962,86 @@ function copyTree(root, viewport, missing) {
|
|
|
610
962
|
}
|
|
611
963
|
}
|
|
612
964
|
const children = [];
|
|
613
|
-
|
|
614
|
-
if (child.type === 'tag') {
|
|
615
|
-
children.push(copy(child, here, depth));
|
|
616
|
-
}
|
|
617
|
-
else if (child.type === 'text')
|
|
618
|
-
children.push(new Text(child.data));
|
|
619
|
-
}
|
|
965
|
+
copyInto(children, el, here, depth, hidden, styles);
|
|
620
966
|
return new Element(strip(el.name), attribs, children);
|
|
621
967
|
};
|
|
622
968
|
return copy(root, true);
|
|
623
969
|
}
|
|
970
|
+
/** What `display: none` takes nothing from: `SvgView` draws none of them
|
|
971
|
+
* where they stand, and what refers to one still finds it (SVG 2, 5.3). */
|
|
972
|
+
const UNDRAWN = new Set([
|
|
973
|
+
'defs',
|
|
974
|
+
'symbol',
|
|
975
|
+
'lineargradient',
|
|
976
|
+
'radialgradient',
|
|
977
|
+
'stop',
|
|
978
|
+
'clippath',
|
|
979
|
+
'mask',
|
|
980
|
+
'pattern',
|
|
981
|
+
'marker',
|
|
982
|
+
'style',
|
|
983
|
+
]);
|
|
984
|
+
/** What `visibility: hidden` leaves undrawn: the elements with something
|
|
985
|
+
* of their own to draw, and a `<use>`, since what it draws inherits the
|
|
986
|
+
* property from it. */
|
|
987
|
+
const HIDEABLE = new Set([
|
|
988
|
+
'path',
|
|
989
|
+
'rect',
|
|
990
|
+
'circle',
|
|
991
|
+
'ellipse',
|
|
992
|
+
'line',
|
|
993
|
+
'polyline',
|
|
994
|
+
'polygon',
|
|
995
|
+
'text',
|
|
996
|
+
'image',
|
|
997
|
+
'use',
|
|
998
|
+
]);
|
|
999
|
+
/** Whether an element is hidden, given whether what it is in is. */
|
|
1000
|
+
function hiddenIn(own, hidden) {
|
|
1001
|
+
const visibility = own?.visibility;
|
|
1002
|
+
return visibility === undefined || visibility === 'inherit'
|
|
1003
|
+
? hidden
|
|
1004
|
+
: visibility === 'hidden';
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* An element's attributes with what the rules give it: each property at
|
|
1008
|
+
* the end of its `style`, where `SvgView` reads it over the attribute of
|
|
1009
|
+
* its name — but `opacity`, which it reads from the attribute alone. One
|
|
1010
|
+
* the rules put back to what the element inherits is taken out of both.
|
|
1011
|
+
*/
|
|
1012
|
+
function restyle(attribs, own) {
|
|
1013
|
+
let style = attribs.style ?? '';
|
|
1014
|
+
let added = '';
|
|
1015
|
+
for (const prop in own) {
|
|
1016
|
+
const value = own[prop];
|
|
1017
|
+
if (prop === 'display' || prop === 'visibility')
|
|
1018
|
+
continue;
|
|
1019
|
+
if (value === 'inherit') {
|
|
1020
|
+
delete attribs[prop];
|
|
1021
|
+
if (style) {
|
|
1022
|
+
style = style
|
|
1023
|
+
.split(';')
|
|
1024
|
+
.filter((d) => d.slice(0, d.indexOf(':')).trim() !== prop)
|
|
1025
|
+
.join(';');
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
else if (prop === 'opacity')
|
|
1029
|
+
attribs.opacity = value;
|
|
1030
|
+
else
|
|
1031
|
+
added += `${prop}:${value};`;
|
|
1032
|
+
}
|
|
1033
|
+
style = style && added ? `${style};${added}` : style || added;
|
|
1034
|
+
if (style)
|
|
1035
|
+
attribs.style = style;
|
|
1036
|
+
else
|
|
1037
|
+
delete attribs.style;
|
|
1038
|
+
}
|
|
624
1039
|
/**
|
|
625
1040
|
* An SVG image's bytes as a drawing, or null for bytes that are not SVG —
|
|
626
1041
|
* which is how a PNG or a JPEG goes on to the image decoder. Nothing names
|
|
627
1042
|
* the type, so it is sniffed: markup, with an `<svg>` root.
|
|
628
1043
|
*/
|
|
629
|
-
export function svgFromBytes(bytes) {
|
|
1044
|
+
export function svgFromBytes(bytes, fragment = '') {
|
|
630
1045
|
let i = bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf ? 3 : 0;
|
|
631
1046
|
while (bytes[i] === 0x20 ||
|
|
632
1047
|
bytes[i] === 0x09 ||
|
|
@@ -642,7 +1057,10 @@ export function svgFromBytes(bytes) {
|
|
|
642
1057
|
try {
|
|
643
1058
|
const doc = parseDocument(text, { xmlMode: true });
|
|
644
1059
|
const root = findRoot(doc.children);
|
|
645
|
-
|
|
1060
|
+
if (!root)
|
|
1061
|
+
return null;
|
|
1062
|
+
const target = fragment ? elementById(doc, fragment) : null;
|
|
1063
|
+
return new SvgDrawing(root, svgIntrinsics(root), true, target);
|
|
646
1064
|
}
|
|
647
1065
|
catch {
|
|
648
1066
|
return null;
|