@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/src/html/resources.ts
CHANGED
|
@@ -182,7 +182,7 @@ export class ResourceStore {
|
|
|
182
182
|
if (!synchronous) this._changed('image');
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
185
|
-
const svg = svgFromBytes(result.bytes);
|
|
185
|
+
const svg = svgFromBytes(result.bytes, fragmentOf(url));
|
|
186
186
|
if (svg) {
|
|
187
187
|
entry.image = svg;
|
|
188
188
|
entry.size = svg.intrinsics;
|
|
@@ -323,3 +323,15 @@ function decodeImage(bytes: Uint8Array): ImageLike | Promise<ImageLike> | null {
|
|
|
323
323
|
return null;
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
|
+
|
|
327
|
+
/** A URL's fragment, decoded, or '' where it has none. */
|
|
328
|
+
function fragmentOf(url: string): string {
|
|
329
|
+
const hash = url.indexOf('#');
|
|
330
|
+
if (hash < 0) return '';
|
|
331
|
+
const fragment = url.slice(hash + 1);
|
|
332
|
+
try {
|
|
333
|
+
return decodeURIComponent(fragment);
|
|
334
|
+
} catch {
|
|
335
|
+
return fragment;
|
|
336
|
+
}
|
|
337
|
+
}
|
package/src/html/svg.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { isSvgRoot, rawTextOf } from './dom.js';
|
|
|
20
20
|
import { Cascade } from './css/cascade.js';
|
|
21
21
|
import { parseMediaQuery, parseStylesheet } from './css/parse.js';
|
|
22
22
|
import type { Stylesheet } from './css/parse.js';
|
|
23
|
+
import { useHref } from './css/shapes.js';
|
|
23
24
|
import type { ShapeStyle, ShapeStyles } from './css/shapes.js';
|
|
24
25
|
import type { RootLook } from './css/style.js';
|
|
25
26
|
import { inkColor, isTransparent, parseColor } from './css/values.js';
|
|
@@ -121,6 +122,36 @@ export function svgIntrinsics(el: Element, fontSize = 16): IntrinsicSize {
|
|
|
121
122
|
return { width, height, ratio };
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
/**
|
|
126
|
+
* An image's size, in device pixels, where nothing sets it: CSS Images 3's
|
|
127
|
+
* default sizing algorithm (5.3.3), against a default object size of
|
|
128
|
+
* `width` by `height` device pixels — a background's positioning area, a
|
|
129
|
+
* list marker's square. Its own size where it has one; the dimension it
|
|
130
|
+
* lacks from its ratio, or else from the default; and one with a ratio
|
|
131
|
+
* alone as large as fits in the default. An SVG may be any of these, and a
|
|
132
|
+
* raster image is the first. `scale` is device pixels per CSS pixel, which
|
|
133
|
+
* the image's own size is in.
|
|
134
|
+
*/
|
|
135
|
+
export function concreteSize(
|
|
136
|
+
size: IntrinsicSize,
|
|
137
|
+
width: number,
|
|
138
|
+
height: number,
|
|
139
|
+
scale: number,
|
|
140
|
+
): [number, number] {
|
|
141
|
+
const { ratio } = size;
|
|
142
|
+
const w = size.width === null ? null : size.width * scale;
|
|
143
|
+
const h = size.height === null ? null : size.height * scale;
|
|
144
|
+
if (w !== null && h !== null) return [w, h];
|
|
145
|
+
if (w !== null) return [w, ratio > 0 ? w / ratio : height];
|
|
146
|
+
if (h !== null) return [ratio > 0 ? h * ratio : width, h];
|
|
147
|
+
if (ratio > 0) {
|
|
148
|
+
return width / height > ratio
|
|
149
|
+
? [height * ratio, height]
|
|
150
|
+
: [width, width / ratio];
|
|
151
|
+
}
|
|
152
|
+
return [width, height];
|
|
153
|
+
}
|
|
154
|
+
|
|
124
155
|
/** The slice of ntk's `SvgView` this draws with. */
|
|
125
156
|
interface SvgViewLike {
|
|
126
157
|
naturalWidth: number;
|
|
@@ -155,6 +186,66 @@ interface ClipContext {
|
|
|
155
186
|
fillRect?(x: number, y: number, w: number, h: number): void;
|
|
156
187
|
}
|
|
157
188
|
|
|
189
|
+
/** A context as a drawing it is lent to sees it (`lend`). */
|
|
190
|
+
interface Lent {
|
|
191
|
+
readonly ctx: ClipContext;
|
|
192
|
+
/** The saves the drawing has made on it and not restored. */
|
|
193
|
+
open: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const LENT = new WeakMap<object, Lent>();
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The context a drawing is lent: the same context, keeping count of the
|
|
200
|
+
* saves the drawing makes on it, so that whatever it leaves saved can be
|
|
201
|
+
* restored after it, and making none of the restores it makes past them.
|
|
202
|
+
*
|
|
203
|
+
* `SvgView` saves the context as it starts to draw, and around an element
|
|
204
|
+
* with a `transform`, and restores in no `finally`: an element it throws
|
|
205
|
+
* on — a colour it cannot read, `fill="var(--c)"` — leaves those saves
|
|
206
|
+
* open. The restore after the drawing then took the last of them for its
|
|
207
|
+
* own save, and the clip to the drawing's box was left on the window's
|
|
208
|
+
* context: everything a later paint drew, in that frame and every frame
|
|
209
|
+
* after, was cut to a sixteen-pixel icon. nextjs.org's blog drew its
|
|
210
|
+
* heading, the icon beside it, and nothing more.
|
|
211
|
+
*/
|
|
212
|
+
function lend(ctx: ClipContext): Lent {
|
|
213
|
+
const known = LENT.get(ctx);
|
|
214
|
+
if (known) return known;
|
|
215
|
+
const bound = new WeakMap<object, unknown>();
|
|
216
|
+
const save = () => {
|
|
217
|
+
ctx.save();
|
|
218
|
+
lent.open += 1;
|
|
219
|
+
};
|
|
220
|
+
const restore = () => {
|
|
221
|
+
if (lent.open === 0) return;
|
|
222
|
+
lent.open -= 1;
|
|
223
|
+
ctx.restore();
|
|
224
|
+
};
|
|
225
|
+
const lent: Lent = {
|
|
226
|
+
open: 0,
|
|
227
|
+
ctx: new Proxy(ctx, {
|
|
228
|
+
get(target, key) {
|
|
229
|
+
if (key === 'save') return save;
|
|
230
|
+
if (key === 'restore') return restore;
|
|
231
|
+
// on the context itself: an accessor, a method and a private field
|
|
232
|
+
// all see the context they belong to
|
|
233
|
+
const value: unknown = Reflect.get(target, key);
|
|
234
|
+
if (typeof value !== 'function') return value;
|
|
235
|
+
let method = bound.get(value);
|
|
236
|
+
if (!method) {
|
|
237
|
+
method = value.bind(target);
|
|
238
|
+
bound.set(value, method);
|
|
239
|
+
}
|
|
240
|
+
return method;
|
|
241
|
+
},
|
|
242
|
+
set: (target, key, value) => Reflect.set(target, key, value),
|
|
243
|
+
}),
|
|
244
|
+
};
|
|
245
|
+
LENT.set(ctx, lent);
|
|
246
|
+
return lent;
|
|
247
|
+
}
|
|
248
|
+
|
|
158
249
|
const ALIGN = /^x(Min|Mid|Max)Y(Min|Mid|Max)$/;
|
|
159
250
|
const AT: Record<string, number> = { Min: 0, Mid: 0.5, Max: 1 };
|
|
160
251
|
|
|
@@ -167,12 +258,14 @@ export class SvgDrawing {
|
|
|
167
258
|
readonly intrinsics: IntrinsicSize;
|
|
168
259
|
private readonly _root: Element;
|
|
169
260
|
private _view: SvgViewLike | null = null;
|
|
170
|
-
/** What the view was last handed the tree for: the root's child count
|
|
171
|
-
* and
|
|
172
|
-
*
|
|
173
|
-
*
|
|
261
|
+
/** What the view was last handed the tree for: the root's child count,
|
|
262
|
+
* and where the root and each element outside it a `<use>` drew ended —
|
|
263
|
+
* a streamed document grows an inline drawing after its first paint,
|
|
264
|
+
* and a sprite's symbol after the first icon drawn from it — and, for a
|
|
265
|
+
* drawing with percentages in it, the viewport they were resolved
|
|
266
|
+
* against. */
|
|
174
267
|
private _seen = -1;
|
|
175
|
-
private
|
|
268
|
+
private _seenEnds: End[] = [];
|
|
176
269
|
private _seenViewport = '';
|
|
177
270
|
/** The `fill` and `stroke` the view's tree was last given its root's,
|
|
178
271
|
* and what the document's rules gave the elements in it. */
|
|
@@ -204,10 +297,20 @@ export class SvgDrawing {
|
|
|
204
297
|
{ cascade: Cascade; own: Map<string, ImagePaint | null> }
|
|
205
298
|
>();
|
|
206
299
|
|
|
207
|
-
|
|
300
|
+
/** The element an SVG image's URL names by its fragment, its own
|
|
301
|
+
* `:target`: a sprite sheet shows the icon `image.svg#icon` names. */
|
|
302
|
+
private readonly _target: Element | null;
|
|
303
|
+
|
|
304
|
+
constructor(
|
|
305
|
+
root: Element,
|
|
306
|
+
intrinsics: IntrinsicSize,
|
|
307
|
+
standalone = false,
|
|
308
|
+
target: Element | null = null,
|
|
309
|
+
) {
|
|
208
310
|
this._root = root;
|
|
209
311
|
this.intrinsics = intrinsics;
|
|
210
312
|
this._standalone = standalone;
|
|
313
|
+
this._target = target;
|
|
211
314
|
}
|
|
212
315
|
|
|
213
316
|
/**
|
|
@@ -261,14 +364,38 @@ export class SvgDrawing {
|
|
|
261
364
|
}
|
|
262
365
|
const box = viewBoxOf(root);
|
|
263
366
|
// the viewport in user units, which a percentage is of
|
|
367
|
+
let vw = w / scale;
|
|
368
|
+
let vh = h / scale;
|
|
369
|
+
// An image with no `viewBox` is laid out at its own size, along an
|
|
370
|
+
// axis it has one on, and stretched to the rectangle along it, as a
|
|
371
|
+
// raster image is: Blink gives it a `viewBox` of its own size that
|
|
372
|
+
// `preserveAspectRatio: none` fits. Drawn at its own size, an icon of
|
|
373
|
+
// `width="24" height="24"` shown 48 wide was a quarter of its box.
|
|
374
|
+
let kx = 1;
|
|
375
|
+
let ky = 1;
|
|
376
|
+
if (!box && this._standalone) {
|
|
377
|
+
const own = this.intrinsics;
|
|
378
|
+
if (own.width !== null && own.width > 0) {
|
|
379
|
+
kx = vw / own.width;
|
|
380
|
+
vw = own.width;
|
|
381
|
+
}
|
|
382
|
+
if (own.height !== null && own.height > 0) {
|
|
383
|
+
ky = vh / own.height;
|
|
384
|
+
vh = own.height;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
264
387
|
const paint =
|
|
265
388
|
(fill === null ? '' : `fill:${fill};`) +
|
|
266
389
|
(stroke === null ? '' : `stroke:${stroke};`);
|
|
267
390
|
const view = box
|
|
268
391
|
? this._viewFor(box[2], box[3], paint, shapes)
|
|
269
|
-
: this._viewFor(
|
|
392
|
+
: this._viewFor(vw, vh, paint, shapes);
|
|
270
393
|
if (!view) return;
|
|
271
394
|
ctx.save();
|
|
395
|
+
// the view draws on the context lent, which counts its saves
|
|
396
|
+
const lent = lend(ctx);
|
|
397
|
+
const outer = lent.open;
|
|
398
|
+
lent.open = 0;
|
|
272
399
|
try {
|
|
273
400
|
ctx.beginPath();
|
|
274
401
|
ctx.rect(x, y, w, h);
|
|
@@ -276,11 +403,11 @@ export class SvgDrawing {
|
|
|
276
403
|
const opts = color ? { color } : undefined;
|
|
277
404
|
if (!box) {
|
|
278
405
|
view.draw(
|
|
279
|
-
ctx,
|
|
406
|
+
lent.ctx,
|
|
280
407
|
x,
|
|
281
408
|
y,
|
|
282
|
-
view.naturalWidth * scale,
|
|
283
|
-
view.naturalHeight * scale,
|
|
409
|
+
view.naturalWidth * scale * kx,
|
|
410
|
+
view.naturalHeight * scale * ky,
|
|
284
411
|
opts,
|
|
285
412
|
);
|
|
286
413
|
} else {
|
|
@@ -289,7 +416,7 @@ export class SvgDrawing {
|
|
|
289
416
|
.split(/\s+/);
|
|
290
417
|
if (fit[0] === 'defer') fit.shift();
|
|
291
418
|
if (fit[0] === 'none') {
|
|
292
|
-
view.draw(ctx, x, y, w, h, opts);
|
|
419
|
+
view.draw(lent.ctx, x, y, w, h, opts);
|
|
293
420
|
} else {
|
|
294
421
|
const align = ALIGN.exec(fit[0] ?? '') ?? ['xMidYMid', 'Mid', 'Mid'];
|
|
295
422
|
const sx = w / box[2];
|
|
@@ -298,7 +425,7 @@ export class SvgDrawing {
|
|
|
298
425
|
const dw = box[2] * s;
|
|
299
426
|
const dh = box[3] * s;
|
|
300
427
|
view.draw(
|
|
301
|
-
ctx,
|
|
428
|
+
lent.ctx,
|
|
302
429
|
x + (w - dw) * AT[align[1]],
|
|
303
430
|
y + (h - dh) * AT[align[2]],
|
|
304
431
|
dw,
|
|
@@ -312,6 +439,11 @@ export class SvgDrawing {
|
|
|
312
439
|
// out of paint on every frame, where nothing could catch it
|
|
313
440
|
this._failed = true;
|
|
314
441
|
} finally {
|
|
442
|
+
// what the drawing saved and did not restore, before this one's own
|
|
443
|
+
// save — which the restore after it would otherwise have taken, and
|
|
444
|
+
// this one's clip been left on the window's context (`lend`)
|
|
445
|
+
for (; lent.open > 0; lent.open -= 1) ctx.restore();
|
|
446
|
+
lent.open = outer;
|
|
315
447
|
ctx.restore();
|
|
316
448
|
}
|
|
317
449
|
}
|
|
@@ -375,6 +507,7 @@ export class SvgDrawing {
|
|
|
375
507
|
null,
|
|
376
508
|
null,
|
|
377
509
|
this._root,
|
|
510
|
+
this._target,
|
|
378
511
|
);
|
|
379
512
|
entry = { cascade, own: new Map() };
|
|
380
513
|
this._cascades.set(made, entry);
|
|
@@ -424,12 +557,14 @@ export class SvgDrawing {
|
|
|
424
557
|
): SvgViewLike | null {
|
|
425
558
|
const root = this._root;
|
|
426
559
|
const count = root.children.length;
|
|
427
|
-
|
|
428
|
-
// more of the
|
|
560
|
+
// more of the document has arrived since a `<use>` found nothing, or
|
|
561
|
+
// more of what the tree was read from: the drawing, a group in it, or
|
|
562
|
+
// a symbol outside it that a chunk ended halfway through
|
|
429
563
|
const arrived =
|
|
430
|
-
this._missingAt !== undefined &&
|
|
431
|
-
|
|
432
|
-
|
|
564
|
+
(this._missingAt !== undefined &&
|
|
565
|
+
lastNode(documentOf(root)) !== this._missingAt) ||
|
|
566
|
+
this._seenEnds.some(moved);
|
|
567
|
+
const grown = count !== this._seen || arrived;
|
|
433
568
|
if (grown || this._percent === null) {
|
|
434
569
|
this._percent = hasPercent(root);
|
|
435
570
|
this._uses = needsExpanding(root);
|
|
@@ -452,16 +587,20 @@ export class SvgDrawing {
|
|
|
452
587
|
}
|
|
453
588
|
try {
|
|
454
589
|
const view = this._view ?? new View(null);
|
|
455
|
-
const
|
|
590
|
+
const reached: Reached = { missing: false, outside: new Set() };
|
|
456
591
|
const tree =
|
|
457
592
|
sized || shapes || root.name.includes(':')
|
|
458
|
-
? copyTree(root, sized ? [width, height] : null,
|
|
593
|
+
? copyTree(root, sized ? [width, height] : null, reached, shapes)
|
|
459
594
|
: root;
|
|
460
595
|
view.setSvgDom(paint ? withPaint(tree, paint) : tree);
|
|
461
|
-
this._missingAt = missing
|
|
596
|
+
this._missingAt = reached.missing
|
|
597
|
+
? lastNode(documentOf(root))
|
|
598
|
+
: undefined;
|
|
599
|
+
const ends = [endOf(root)];
|
|
600
|
+
for (const el of reached.outside) ends.push(endOf(el));
|
|
462
601
|
this._view = view;
|
|
463
602
|
this._seen = count;
|
|
464
|
-
this.
|
|
603
|
+
this._seenEnds = ends;
|
|
465
604
|
this._seenViewport = viewport;
|
|
466
605
|
this._seenPaint = painted;
|
|
467
606
|
return view;
|
|
@@ -641,13 +780,6 @@ function hasPercent(root: Element): boolean {
|
|
|
641
780
|
return walk(root);
|
|
642
781
|
}
|
|
643
782
|
|
|
644
|
-
/** The element a `<use>` refers to by a fragment, its id: one in another
|
|
645
|
-
* document is none here, where nothing is fetched for a drawing. */
|
|
646
|
-
function useTarget(el: Element): string | null {
|
|
647
|
-
const href = el.attribs.href ?? el.attribs['xlink:href'] ?? '';
|
|
648
|
-
return href.length > 1 && href.startsWith('#') ? href.slice(1) : null;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
783
|
/** The elements under a root by their ids, the first of each. */
|
|
652
784
|
function idsUnder(root: Element): Map<string, Element> {
|
|
653
785
|
const ids = new Map<string, Element>();
|
|
@@ -667,10 +799,10 @@ function idsUnder(root: Element): Map<string, Element> {
|
|
|
667
799
|
/**
|
|
668
800
|
* Whether a `<use>` under the root is one `SvgView` cannot draw as it
|
|
669
801
|
* stands: it looks a reference up among the root's own elements, and draws
|
|
670
|
-
* a `<symbol>` as its children where they are
|
|
671
|
-
* outside the root — an icon sprite's, a hidden
|
|
672
|
-
* top of the page — and one to a symbol with a
|
|
673
|
-
* drawn from a copy (`copyTree`).
|
|
802
|
+
* a `<symbol>` as its children where they are, with the `<use>`'s paint.
|
|
803
|
+
* So one to an element outside the root — an icon sprite's, a hidden
|
|
804
|
+
* `<svg>` of symbols at the top of the page — and one to a symbol with a
|
|
805
|
+
* `viewBox` or a paint of its own are drawn from a copy (`copyTree`).
|
|
674
806
|
*/
|
|
675
807
|
function needsExpanding(root: Element): boolean {
|
|
676
808
|
let ids: Map<string, Element> | null = null;
|
|
@@ -679,12 +811,15 @@ function needsExpanding(root: Element): boolean {
|
|
|
679
811
|
if (child.type !== 'tag') continue;
|
|
680
812
|
const tag = child as Element;
|
|
681
813
|
if (localName(tag.name) === 'use') {
|
|
682
|
-
const id =
|
|
814
|
+
const id = useHref(tag);
|
|
683
815
|
if (id !== null) {
|
|
684
816
|
ids ??= idsUnder(root);
|
|
685
817
|
const target = ids.get(id);
|
|
686
818
|
if (!target) return true;
|
|
687
|
-
if (
|
|
819
|
+
if (
|
|
820
|
+
localName(target.name) === 'symbol' &&
|
|
821
|
+
(viewBoxOf(target) || symbolOwn(target))
|
|
822
|
+
) {
|
|
688
823
|
return true;
|
|
689
824
|
}
|
|
690
825
|
}
|
|
@@ -710,6 +845,29 @@ function lastNode(top: ParentNode): ChildNode | null {
|
|
|
710
845
|
return last;
|
|
711
846
|
}
|
|
712
847
|
|
|
848
|
+
/** Where a part of a document ends: its last node, and the length of that
|
|
849
|
+
* node's text where it is text, since a chunk's text goes on the end of
|
|
850
|
+
* the text before it (domhandler's `ontext`) and leaves the node as it
|
|
851
|
+
* was. What a document that is still arriving appends to the part moves
|
|
852
|
+
* it, and what it appends after the part does not. */
|
|
853
|
+
interface End {
|
|
854
|
+
top: ParentNode;
|
|
855
|
+
last: ChildNode | null;
|
|
856
|
+
text: number;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function endOf(top: ParentNode): End {
|
|
860
|
+
const last = lastNode(top);
|
|
861
|
+
return { top, last, text: last?.type === 'text' ? last.data.length : -1 };
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/** Whether more of a part has arrived since its `End` was taken. */
|
|
865
|
+
function moved(end: End): boolean {
|
|
866
|
+
const last = lastNode(end.top);
|
|
867
|
+
if (last !== end.last) return true;
|
|
868
|
+
return last?.type === 'text' && last.data.length !== end.text;
|
|
869
|
+
}
|
|
870
|
+
|
|
713
871
|
/** The first element of a document with an id, in its order. */
|
|
714
872
|
function elementById(top: ParentNode, id: string): Element | null {
|
|
715
873
|
const stack: ChildNode[] = [...top.children].reverse();
|
|
@@ -772,6 +930,51 @@ const USE_PLACEMENT = new Set([
|
|
|
772
930
|
'height',
|
|
773
931
|
]);
|
|
774
932
|
|
|
933
|
+
/** A `<symbol>`'s attributes that paint what is in it: its presentation
|
|
934
|
+
* attributes and its `style` (SVG 2, 6.3). Not where it goes, the viewport
|
|
935
|
+
* it makes or a name, which a symbol's copy is no group to have. */
|
|
936
|
+
const SYMBOL_PAINTS = new Set([
|
|
937
|
+
'style',
|
|
938
|
+
'fill',
|
|
939
|
+
'fill-opacity',
|
|
940
|
+
'fill-rule',
|
|
941
|
+
'stroke',
|
|
942
|
+
'stroke-width',
|
|
943
|
+
'stroke-linecap',
|
|
944
|
+
'stroke-linejoin',
|
|
945
|
+
'stroke-miterlimit',
|
|
946
|
+
'stroke-dasharray',
|
|
947
|
+
'stroke-dashoffset',
|
|
948
|
+
'stroke-opacity',
|
|
949
|
+
'color',
|
|
950
|
+
'opacity',
|
|
951
|
+
'visibility',
|
|
952
|
+
'display',
|
|
953
|
+
'font-family',
|
|
954
|
+
'font-size',
|
|
955
|
+
'font-style',
|
|
956
|
+
'font-weight',
|
|
957
|
+
'text-anchor',
|
|
958
|
+
]);
|
|
959
|
+
|
|
960
|
+
/** A `<symbol>`'s own attributes that paint what is in it, or null where
|
|
961
|
+
* it has none: Chrome draws a sprite's `<symbol fill="…">` in that fill,
|
|
962
|
+
* where `SvgView` draws a symbol's children alone. */
|
|
963
|
+
function symbolOwn(symbol: Element): Record<string, string> | null {
|
|
964
|
+
let kept: Record<string, string> | null = null;
|
|
965
|
+
for (const name in symbol.attribs) {
|
|
966
|
+
if (SYMBOL_PAINTS.has(name)) (kept ??= {})[name] = symbol.attribs[name];
|
|
967
|
+
}
|
|
968
|
+
return kept;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/** What a copy of a drawing reached outside its root: whether a `<use>`
|
|
972
|
+
* in it found nothing, and the elements one was drawn from. */
|
|
973
|
+
interface Reached {
|
|
974
|
+
missing: boolean;
|
|
975
|
+
outside: Set<Element>;
|
|
976
|
+
}
|
|
977
|
+
|
|
775
978
|
/**
|
|
776
979
|
* The tree `SvgView` reads, where the document's own will not do: with
|
|
777
980
|
* local names — `SvgView` knows `rect`, not `svg:rect`, and a prefix is
|
|
@@ -781,8 +984,9 @@ const USE_PLACEMENT = new Set([
|
|
|
781
984
|
* group of what it refers to (SVG 2, 5.5): the element, from wherever in
|
|
782
985
|
* the document it is, or a symbol's children, its `viewBox` fitted to the
|
|
783
986
|
* viewport the `<use>` gives it — its `width` and `height`, and all of the
|
|
784
|
-
* drawing's where it has none. `
|
|
785
|
-
* element the document does not have
|
|
987
|
+
* drawing's where it has none. `reached` is told where one refers to an
|
|
988
|
+
* element the document does not have, and of each element outside the root
|
|
989
|
+
* that one was drawn from.
|
|
786
990
|
*
|
|
787
991
|
* And with what the document's rules give each element (`shapes`), where
|
|
788
992
|
* `SvgView` reads an element's own: at the end of its `style`, over its
|
|
@@ -790,12 +994,15 @@ const USE_PLACEMENT = new Set([
|
|
|
790
994
|
* cascade that made them has weighed already. An element a rule gives
|
|
791
995
|
* `display: none` is left out, with what is in it, and a shape one hides
|
|
792
996
|
* — `visibility` is inherited, and a shape in a hidden group may be shown
|
|
793
|
-
* again — is left out alone.
|
|
997
|
+
* again — is left out alone. What a `<use>` draws has what the rules give
|
|
998
|
+
* its copy (`ShapeStyles.used`), which may not be what they give the
|
|
999
|
+
* element where it stands, so where they give anything, every `<use>` is
|
|
1000
|
+
* drawn from a copy here.
|
|
794
1001
|
*/
|
|
795
1002
|
function copyTree(
|
|
796
1003
|
root: Element,
|
|
797
1004
|
viewport: [number, number] | null,
|
|
798
|
-
|
|
1005
|
+
reached?: Reached,
|
|
799
1006
|
shapes: ShapeStyles | null = null,
|
|
800
1007
|
): Element {
|
|
801
1008
|
const colon = root.name.indexOf(':');
|
|
@@ -811,25 +1018,31 @@ function copyTree(
|
|
|
811
1018
|
resolve: boolean,
|
|
812
1019
|
depth: number,
|
|
813
1020
|
hidden: boolean,
|
|
1021
|
+
/** What the rules give the `<use>`. */
|
|
1022
|
+
own: ShapeStyle | undefined,
|
|
814
1023
|
): Element | null => {
|
|
815
|
-
const id =
|
|
1024
|
+
const id = useHref(use);
|
|
816
1025
|
if (id === null || depth >= USE_DEPTH) return null;
|
|
817
1026
|
ids ??= idsUnder(root);
|
|
818
1027
|
const inside = ids.get(id);
|
|
819
1028
|
const target = inside ?? elementById(documentOf(root), id);
|
|
820
1029
|
if (!target) {
|
|
821
|
-
if (
|
|
1030
|
+
if (reached) reached.missing = true;
|
|
822
1031
|
return null;
|
|
823
1032
|
}
|
|
1033
|
+
if (!inside) reached?.outside.add(target);
|
|
824
1034
|
const symbol = localName(target.name) === 'symbol';
|
|
825
1035
|
const box = symbol ? viewBoxOf(target) : null;
|
|
826
|
-
|
|
827
|
-
|
|
1036
|
+
const kept = symbol ? symbolOwn(target) : null;
|
|
1037
|
+
// one of the root's own that `SvgView` draws as it is: with no
|
|
1038
|
+
// viewport to fit, no paint of a symbol's own, and no rule's styles
|
|
1039
|
+
if (inside && !box && !kept && !shapes) return null;
|
|
1040
|
+
// what the rules give the copy, element by element
|
|
1041
|
+
const styles = shapes?.used?.get(use) ?? null;
|
|
828
1042
|
const attribs: Record<string, string> = {};
|
|
829
1043
|
for (const name in use.attribs) {
|
|
830
1044
|
if (!USE_PLACEMENT.has(name)) attribs[name] = use.attribs[name];
|
|
831
1045
|
}
|
|
832
|
-
const own = shapes?.of.get(use);
|
|
833
1046
|
if (own) restyle(attribs, own);
|
|
834
1047
|
const x = parseFloat(use.attribs.x ?? '') || 0;
|
|
835
1048
|
const y = parseFloat(use.attribs.y ?? '') || 0;
|
|
@@ -857,17 +1070,38 @@ function copyTree(
|
|
|
857
1070
|
// a length in a symbol with a `viewBox` is of that, and left as it is
|
|
858
1071
|
const within = resolve && !box;
|
|
859
1072
|
const children: ChildNode[] = [];
|
|
860
|
-
if (symbol)
|
|
861
|
-
|
|
862
|
-
|
|
1073
|
+
if (symbol) {
|
|
1074
|
+
// The symbol is the top of the copy, and paints what is in it: its
|
|
1075
|
+
// attributes, and what the rules give it there. `SvgView` draws a
|
|
1076
|
+
// symbol's children alone, so they go in a group that is it. Where
|
|
1077
|
+
// it stands a symbol is drawn by nobody, so `display: none` takes
|
|
1078
|
+
// nothing from it there (`UNDRAWN`); at the top of a copy it takes
|
|
1079
|
+
// the copy, as Chrome has it.
|
|
1080
|
+
const mine = styles?.get(target);
|
|
1081
|
+
const inner = hiddenIn(mine, hidden);
|
|
1082
|
+
if (mine?.display !== 'none') {
|
|
1083
|
+
copyInto(children, target, within, depth + 1, inner, styles);
|
|
1084
|
+
}
|
|
1085
|
+
if (kept || mine) {
|
|
1086
|
+
const paints = { ...kept };
|
|
1087
|
+
if (mine) restyle(paints, mine);
|
|
1088
|
+
const group = new Element('g', paints, children.splice(0));
|
|
1089
|
+
children.push(group);
|
|
1090
|
+
}
|
|
1091
|
+
} else if (shown(target, hidden, styles)) {
|
|
1092
|
+
children.push(copy(target, within, depth + 1, hidden, styles));
|
|
863
1093
|
}
|
|
864
1094
|
return new Element('g', attribs, children);
|
|
865
1095
|
};
|
|
866
1096
|
/** Whether an element is drawn at all, in a group that is hidden or
|
|
867
1097
|
* not: one no rule gives `display: none`, and no shape a rule hides. */
|
|
868
|
-
const shown = (
|
|
869
|
-
|
|
870
|
-
|
|
1098
|
+
const shown = (
|
|
1099
|
+
el: Element,
|
|
1100
|
+
hidden: boolean,
|
|
1101
|
+
styles: ReadonlyMap<Element, ShapeStyle> | null,
|
|
1102
|
+
): boolean => {
|
|
1103
|
+
if (!styles) return true;
|
|
1104
|
+
const own = styles.get(el);
|
|
871
1105
|
const name = localName(el.name);
|
|
872
1106
|
if (own?.display === 'none' && !UNDRAWN.has(name)) return false;
|
|
873
1107
|
return !(hiddenIn(own, hidden) && HIDEABLE.has(name));
|
|
@@ -878,11 +1112,12 @@ function copyTree(
|
|
|
878
1112
|
resolve: boolean,
|
|
879
1113
|
depth: number,
|
|
880
1114
|
hidden: boolean,
|
|
1115
|
+
styles: ReadonlyMap<Element, ShapeStyle> | null,
|
|
881
1116
|
): void => {
|
|
882
1117
|
for (const child of el.children) {
|
|
883
1118
|
if (child.type === 'tag') {
|
|
884
|
-
if (shown(child as Element, hidden)) {
|
|
885
|
-
children.push(copy(child as Element, resolve, depth, hidden));
|
|
1119
|
+
if (shown(child as Element, hidden, styles)) {
|
|
1120
|
+
children.push(copy(child as Element, resolve, depth, hidden, styles));
|
|
886
1121
|
}
|
|
887
1122
|
} else if (child.type === 'text') children.push(new Text(child.data));
|
|
888
1123
|
}
|
|
@@ -893,11 +1128,14 @@ function copyTree(
|
|
|
893
1128
|
depth = 0,
|
|
894
1129
|
/** Whether the element inherits a `visibility` of `hidden`. */
|
|
895
1130
|
hidden = false,
|
|
1131
|
+
/** What the rules give the elements of the tree it is in: the
|
|
1132
|
+
* drawing's, or a `<use>`'s copy's. */
|
|
1133
|
+
styles: ReadonlyMap<Element, ShapeStyle> | null = shapes?.of ?? null,
|
|
896
1134
|
): Element => {
|
|
897
|
-
const own =
|
|
1135
|
+
const own = styles?.get(el);
|
|
898
1136
|
hidden = hiddenIn(own, hidden);
|
|
899
1137
|
if (localName(el.name) === 'use') {
|
|
900
|
-
const group = expand(el, resolve, depth, hidden);
|
|
1138
|
+
const group = expand(el, resolve, depth, hidden, own);
|
|
901
1139
|
if (group) return group;
|
|
902
1140
|
}
|
|
903
1141
|
const here = resolve && !OWN_UNITS.has(localName(el.name));
|
|
@@ -916,7 +1154,7 @@ function copyTree(
|
|
|
916
1154
|
}
|
|
917
1155
|
}
|
|
918
1156
|
const children: ChildNode[] = [];
|
|
919
|
-
copyInto(children, el, here, depth, hidden);
|
|
1157
|
+
copyInto(children, el, here, depth, hidden, styles);
|
|
920
1158
|
return new Element(strip(el.name), attribs, children);
|
|
921
1159
|
};
|
|
922
1160
|
return copy(root, true);
|
|
@@ -994,7 +1232,10 @@ function restyle(attribs: Record<string, string>, own: ShapeStyle): void {
|
|
|
994
1232
|
* which is how a PNG or a JPEG goes on to the image decoder. Nothing names
|
|
995
1233
|
* the type, so it is sniffed: markup, with an `<svg>` root.
|
|
996
1234
|
*/
|
|
997
|
-
export function svgFromBytes(
|
|
1235
|
+
export function svgFromBytes(
|
|
1236
|
+
bytes: Uint8Array,
|
|
1237
|
+
fragment = '',
|
|
1238
|
+
): SvgDrawing | null {
|
|
998
1239
|
let i = bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf ? 3 : 0;
|
|
999
1240
|
while (
|
|
1000
1241
|
bytes[i] === 0x20 ||
|
|
@@ -1010,7 +1251,9 @@ export function svgFromBytes(bytes: Uint8Array): SvgDrawing | null {
|
|
|
1010
1251
|
try {
|
|
1011
1252
|
const doc = parseDocument(text, { xmlMode: true });
|
|
1012
1253
|
const root = findRoot(doc.children);
|
|
1013
|
-
|
|
1254
|
+
if (!root) return null;
|
|
1255
|
+
const target = fragment ? elementById(doc, fragment) : null;
|
|
1256
|
+
return new SvgDrawing(root, svgIntrinsics(root), true, target);
|
|
1014
1257
|
} catch {
|
|
1015
1258
|
return null;
|
|
1016
1259
|
}
|