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.
@@ -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 (features !== undefined && features.split(/\s+/).some((f) => UNDRAWN_FEATURES.has(f))) return false;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntk",
3
- "version": "8.25.0",
3
+ "version": "8.26.0",
4
4
  "description": "Desktop UI toolkit for X11 with canvas-like 2d and OpenGL rendering",
5
5
  "author": "Andrey Sidorov <sidorares@yandex.ru>",
6
6
  "license": "MIT",