ntk 8.25.0 → 8.26.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/lib/widgets/svgview.js +90 -11
- package/package.json +1 -1
package/lib/widgets/svgview.js
CHANGED
|
@@ -179,6 +179,8 @@ function userLanguages() {
|
|
|
179
179
|
return (defaultLanguages = out);
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
const EXTENSIBILITY = 'http://www.w3.org/TR/SVG11/feature#Extensibility';
|
|
183
|
+
|
|
182
184
|
/**
|
|
183
185
|
* Whether an element's conditional processing attributes hold (SVG 1.1,
|
|
184
186
|
* 5.8): where one does not, the element and all of it are not rendered, and
|
|
@@ -190,14 +192,21 @@ function userLanguages() {
|
|
|
190
192
|
* of. SVG 2 dropped the attribute and browsers hold every one, but the
|
|
191
193
|
* one they would pick, a `foreignObject`, draws nothing here, and the
|
|
192
194
|
* `<text>` an exporter writes after it for renderers like this one does.
|
|
195
|
+
* `extensible` is whether a `foreignObject` is drawn — the draw was handed
|
|
196
|
+
* `opts.foreignObject` — and then `#Extensibility` holds.
|
|
193
197
|
* - `systemLanguage` holds where one of its comma-separated tags is one of
|
|
194
198
|
* `languages`, or that narrowed by a subtag, or the other way round.
|
|
195
199
|
*/
|
|
196
|
-
function conditionsHold(node, languages) {
|
|
200
|
+
function conditionsHold(node, languages, extensible = false) {
|
|
197
201
|
if (!node.attribs) return true;
|
|
198
202
|
if (attr(node, 'requiredExtensions') !== undefined) return false;
|
|
199
203
|
const features = attr(node, 'requiredFeatures');
|
|
200
|
-
if (
|
|
204
|
+
if (
|
|
205
|
+
features !== undefined &&
|
|
206
|
+
features.split(/\s+/).some((f) => UNDRAWN_FEATURES.has(f) && !(extensible && f === EXTENSIBILITY))
|
|
207
|
+
) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
201
210
|
const system = attr(node, 'systemLanguage');
|
|
202
211
|
if (system !== undefined) {
|
|
203
212
|
const tags = system.split(',').map((t) => t.trim().toLowerCase());
|
|
@@ -212,9 +221,9 @@ function conditionsHold(node, languages) {
|
|
|
212
221
|
* (SVG 1.1, 5.8.2), whatever that is — a `display: none` one is chosen, and
|
|
213
222
|
* draws nothing — or null where none does.
|
|
214
223
|
*/
|
|
215
|
-
function switchChoice(node, languages) {
|
|
224
|
+
function switchChoice(node, languages, extensible = false) {
|
|
216
225
|
for (const child of node.children || []) {
|
|
217
|
-
if (child.type === 'tag' && conditionsHold(child, languages)) return child;
|
|
226
|
+
if (child.type === 'tag' && conditionsHold(child, languages, extensible)) return child;
|
|
218
227
|
}
|
|
219
228
|
return null;
|
|
220
229
|
}
|
|
@@ -254,6 +263,24 @@ function viewportLength(node, name, size, fallback) {
|
|
|
254
263
|
return v.endsWith('%') ? (n / 100) * size : n;
|
|
255
264
|
}
|
|
256
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Where a `<foreignObject>` is in the user space it is drawn in (SVG 2,
|
|
268
|
+
* 23.2): its `x` and `y`, 0 where unset, and its `width` and `height`, 0
|
|
269
|
+
* where unset or `auto` — which draws nothing — each a length or a
|
|
270
|
+
* percentage of the viewport, `[width, height]`. A negative size is as
|
|
271
|
+
* none. Its bounding box, which `objectBoundingBox` units are of.
|
|
272
|
+
*/
|
|
273
|
+
function foreignBox(node, [pw, ph]) {
|
|
274
|
+
const width = viewportLength(node, 'width', pw, 0);
|
|
275
|
+
const height = viewportLength(node, 'height', ph, 0);
|
|
276
|
+
return {
|
|
277
|
+
x: viewportLength(node, 'x', pw, 0),
|
|
278
|
+
y: viewportLength(node, 'y', ph, 0),
|
|
279
|
+
width: width > 0 ? width : 0,
|
|
280
|
+
height: height > 0 ? height : 0
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
257
284
|
const ALIGN = /^x(Min|Mid|Max)Y(Min|Mid|Max)$/;
|
|
258
285
|
const ALONG = { Min: 0, Mid: 0.5, Max: 1 };
|
|
259
286
|
|
|
@@ -704,6 +731,10 @@ function scanPaints(root, ids, languages) {
|
|
|
704
731
|
if (chosen) visit(chosen, style, depth + 1);
|
|
705
732
|
return;
|
|
706
733
|
}
|
|
734
|
+
case 'foreignobject':
|
|
735
|
+
// it paints nothing of its own: what is in it is the caller's to
|
|
736
|
+
// draw (`opts.foreignObject`), in the caller's colours
|
|
737
|
+
return;
|
|
707
738
|
case 'use': {
|
|
708
739
|
const href = node.attribs?.href || node.attribs?.['xlink:href'] || '';
|
|
709
740
|
const target = href.startsWith('#') ? ids.get(href.slice(1)) : null;
|
|
@@ -782,6 +813,10 @@ export default class SvgView {
|
|
|
782
813
|
this._root = null;
|
|
783
814
|
this._ids = new Map();
|
|
784
815
|
this._viewport = [0, 0];
|
|
816
|
+
/** what draws a `<foreignObject>` in this draw (`opts.foreignObject`),
|
|
817
|
+
* and whether there is one, which `#Extensibility` holds where */
|
|
818
|
+
this._foreignObject = null;
|
|
819
|
+
this._extensible = false;
|
|
785
820
|
this.naturalWidth = 0;
|
|
786
821
|
this.naturalHeight = 0;
|
|
787
822
|
this.viewBox = null;
|
|
@@ -897,9 +932,23 @@ export default class SvgView {
|
|
|
897
932
|
* user units: an inline `<svg>` takes its font from the page around it,
|
|
898
933
|
* as it does in a browser, where on its own it starts from 16px
|
|
899
934
|
* sans-serif.
|
|
935
|
+
*
|
|
936
|
+
* `opts.foreignObject(element, ctx, box)` draws what a `<foreignObject>`
|
|
937
|
+
* holds, which is the caller's — HTML, in a host document's inline
|
|
938
|
+
* `<svg>` — where the drawing reaches the object in its order: over what
|
|
939
|
+
* is drawn before it and under what is drawn after. `ctx` is in the
|
|
940
|
+
* object's user space, its `transform` and every one around it in force
|
|
941
|
+
* and cut to the `clip-path` on it and on what it is in, its `globalAlpha`
|
|
942
|
+
* what the opacities down to it come to. `box` is `{ x, y, width, height,
|
|
943
|
+
* opacity }`: where the object is in that space, its bounding box, and
|
|
944
|
+
* that opacity. The context is restored after it. A hidden object, or one
|
|
945
|
+
* of no width or height, is not handed over; and where it is given,
|
|
946
|
+
* `#Extensibility` holds in a `requiredFeatures`.
|
|
900
947
|
*/
|
|
901
948
|
draw(ctx, x = 0, y = 0, w = this.naturalWidth, h = this.naturalHeight, opts = {}) {
|
|
902
949
|
if (!this._root) return;
|
|
950
|
+
this._foreignObject = typeof opts.foreignObject === 'function' ? opts.foreignObject : null;
|
|
951
|
+
this._extensible = this._foreignObject !== null;
|
|
903
952
|
// what a masked element's surface is made with, and where it can show
|
|
904
953
|
this._surface = opts.surface ?? surfaceMaker(ctx) ?? surfaceMaker(this._ctx);
|
|
905
954
|
this._bounds = deviceBox(ctx, x, y, w, h);
|
|
@@ -925,6 +974,8 @@ export default class SvgView {
|
|
|
925
974
|
this._renderChildren(this._root, ctx, this._style(this._root, this._initial), 1, 0);
|
|
926
975
|
} finally {
|
|
927
976
|
this._surface = null;
|
|
977
|
+
this._foreignObject = null;
|
|
978
|
+
this._extensible = false;
|
|
928
979
|
ctx.restore();
|
|
929
980
|
}
|
|
930
981
|
}
|
|
@@ -1152,7 +1203,7 @@ export default class SvgView {
|
|
|
1152
1203
|
_renderNode(node, ctx, parentStyle, alpha, depth) {
|
|
1153
1204
|
if (depth > 32) return;
|
|
1154
1205
|
const name = tag(node);
|
|
1155
|
-
if (NON_RENDERED.has(name) || displayNone(node) || !conditionsHold(node, this.languages)) return;
|
|
1206
|
+
if (NON_RENDERED.has(name) || displayNone(node) || !conditionsHold(node, this.languages, this._extensible)) return;
|
|
1156
1207
|
|
|
1157
1208
|
const style = this._style(node, parentStyle);
|
|
1158
1209
|
// not inherited: a group's multiplies down through what is in it
|
|
@@ -1191,7 +1242,7 @@ export default class SvgView {
|
|
|
1191
1242
|
case 'switch': {
|
|
1192
1243
|
// a group of one: its transform and opacity are a group's, and it
|
|
1193
1244
|
// draws the first child whose conditions hold and none of the rest
|
|
1194
|
-
const chosen = switchChoice(node, this.languages);
|
|
1245
|
+
const chosen = switchChoice(node, this.languages, this._extensible);
|
|
1195
1246
|
if (chosen) this._renderNode(chosen, ctx, style, nodeAlpha, depth + 1);
|
|
1196
1247
|
break;
|
|
1197
1248
|
}
|
|
@@ -1209,6 +1260,9 @@ export default class SvgView {
|
|
|
1209
1260
|
// `<text>` may be shown again
|
|
1210
1261
|
this._renderText(node, ctx, style, nodeAlpha);
|
|
1211
1262
|
break;
|
|
1263
|
+
case 'foreignobject':
|
|
1264
|
+
this._renderForeign(node, ctx, style, nodeAlpha);
|
|
1265
|
+
break;
|
|
1212
1266
|
default: {
|
|
1213
1267
|
// `visibility` hides what an element paints itself, and is
|
|
1214
1268
|
// inherited: a group walks on, since what is in it may say
|
|
@@ -1245,6 +1299,26 @@ export default class SvgView {
|
|
|
1245
1299
|
}
|
|
1246
1300
|
}
|
|
1247
1301
|
|
|
1302
|
+
/**
|
|
1303
|
+
* A `<foreignObject>` (SVG 2, 23.2), handed to `opts.foreignObject` to
|
|
1304
|
+
* draw what it holds, in its user space and at its opacity — the caller's
|
|
1305
|
+
* own drawing, so the context is put back as it was after it, whatever
|
|
1306
|
+
* it set. Nothing is drawn of one where the draw was handed none.
|
|
1307
|
+
*/
|
|
1308
|
+
_renderForeign(node, ctx, style, alpha) {
|
|
1309
|
+
const draw = this._foreignObject;
|
|
1310
|
+
if (!draw || style.visibility !== 'visible') return;
|
|
1311
|
+
const box = foreignBox(node, this._viewport);
|
|
1312
|
+
if (!(box.width > 0 && box.height > 0)) return;
|
|
1313
|
+
ctx.save();
|
|
1314
|
+
try {
|
|
1315
|
+
ctx.globalAlpha = alpha;
|
|
1316
|
+
draw(node, ctx, { ...box, opacity: alpha });
|
|
1317
|
+
} finally {
|
|
1318
|
+
ctx.restore();
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1248
1322
|
/**
|
|
1249
1323
|
* The markers a shape's `marker-start`, `marker-mid` and `marker-end`
|
|
1250
1324
|
* name, drawn at its vertices (SVG 2, 11.6): the first vertex of the
|
|
@@ -1534,7 +1608,7 @@ export default class SvgView {
|
|
|
1534
1608
|
* or that is not visible.
|
|
1535
1609
|
*/
|
|
1536
1610
|
_clipPart(child, inherited, base, ctx, depth, nest) {
|
|
1537
|
-
if (!CLIPPING.has(tag(child)) || displayNone(child) || !conditionsHold(child, this.languages)) return null;
|
|
1611
|
+
if (!CLIPPING.has(tag(child)) || displayNone(child) || !conditionsHold(child, this.languages, this._extensible)) return null;
|
|
1538
1612
|
let style = this._style(child, inherited);
|
|
1539
1613
|
let m = matMultiply(base, matrixOf(ownTransforms(child)));
|
|
1540
1614
|
const clips = [];
|
|
@@ -1548,7 +1622,7 @@ export default class SvgView {
|
|
|
1548
1622
|
if (tag(child) === 'use') {
|
|
1549
1623
|
shape = this._useTarget(child);
|
|
1550
1624
|
if (!shape || tag(shape) === 'use' || !CLIPPING.has(tag(shape))) return null;
|
|
1551
|
-
if (displayNone(shape) || !conditionsHold(shape, this.languages)) return null;
|
|
1625
|
+
if (displayNone(shape) || !conditionsHold(shape, this.languages, this._extensible)) return null;
|
|
1552
1626
|
style = this._style(shape, style);
|
|
1553
1627
|
m = matMultiply(m, matrixOf(ownTransforms(shape)));
|
|
1554
1628
|
cutBy(shape, style, m);
|
|
@@ -1745,11 +1819,11 @@ export default class SvgView {
|
|
|
1745
1819
|
case 'g':
|
|
1746
1820
|
case 'a':
|
|
1747
1821
|
case 'switch': {
|
|
1748
|
-
const kids = name === 'switch' ? [switchChoice(node, this.languages)] : node.children || [];
|
|
1822
|
+
const kids = name === 'switch' ? [switchChoice(node, this.languages, this._extensible)] : node.children || [];
|
|
1749
1823
|
let box = null;
|
|
1750
1824
|
for (const child of kids) {
|
|
1751
1825
|
if (!child || child.type !== 'tag') continue;
|
|
1752
|
-
if (NON_RENDERED.has(tag(child)) || displayNone(child) || !conditionsHold(child, this.languages)) continue;
|
|
1826
|
+
if (NON_RENDERED.has(tag(child)) || displayNone(child) || !conditionsHold(child, this.languages, this._extensible)) continue;
|
|
1753
1827
|
const inner = this._bbox(child, ctx, this._style(child, style), depth + 1);
|
|
1754
1828
|
if (inner) box = unionBox(box, transformedBox(inner, ownTransforms(child)));
|
|
1755
1829
|
}
|
|
@@ -1780,6 +1854,11 @@ export default class SvgView {
|
|
|
1780
1854
|
ctx.restore();
|
|
1781
1855
|
}
|
|
1782
1856
|
}
|
|
1857
|
+
case 'foreignobject': {
|
|
1858
|
+
// its rectangle, whether or not anything draws what is in it
|
|
1859
|
+
const { x, y, width, height } = foreignBox(node, this._viewport);
|
|
1860
|
+
return { x, y, w: width, h: height };
|
|
1861
|
+
}
|
|
1783
1862
|
default: {
|
|
1784
1863
|
const path = shapePath(node);
|
|
1785
1864
|
return path ? pathBBox(path) : null;
|
|
@@ -1831,7 +1910,7 @@ export default class SvgView {
|
|
|
1831
1910
|
} else if (child.type === 'cdata') {
|
|
1832
1911
|
walk(child.children);
|
|
1833
1912
|
} else if (child.type === 'tag' && depth < 32) {
|
|
1834
|
-
if (!TEXT_SPANS.has(tag(child)) || displayNone(child) || !conditionsHold(child, this.languages)) continue;
|
|
1913
|
+
if (!TEXT_SPANS.has(tag(child)) || displayNone(child) || !conditionsHold(child, this.languages, this._extensible)) continue;
|
|
1835
1914
|
const ca = a * (ownProperty(child, 'opacity', alphaValue) ?? 1);
|
|
1836
1915
|
collect(child, this._style(child, st), ca, depth + 1);
|
|
1837
1916
|
}
|