@vectojs/core 1.24.0 → 1.25.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/{chunk-ME4LB2HB.mjs → chunk-I4WQY7CJ.mjs} +15 -0
- package/dist/{chunk-KEBYJVD6.js → chunk-KHGHP2J3.js} +15 -0
- package/dist/{chunk-FRMLD4PP.mjs → chunk-MVFPN4Y5.mjs} +114 -1
- package/dist/{chunk-AGP4VLF4.js → chunk-Z3HFY75R.js} +131 -18
- package/dist/index.js +39 -39
- package/dist/index.mjs +5 -5
- package/dist/renderer.js +2 -2
- package/dist/renderer.mjs +1 -1
- package/dist/text/MSDFTextEntity.d.ts +24 -0
- package/dist/text/SVGEntity.d.ts +24 -0
- package/dist/text.js +2 -2
- package/dist/text.mjs +1 -1
- package/package.json +6 -6
|
@@ -1210,6 +1210,16 @@ function grow(data, needed) {
|
|
|
1210
1210
|
grown.set(data);
|
|
1211
1211
|
return grown;
|
|
1212
1212
|
}
|
|
1213
|
+
function isSourceReady(source) {
|
|
1214
|
+
const candidate = source;
|
|
1215
|
+
if (typeof candidate.complete === "boolean") {
|
|
1216
|
+
if (!candidate.complete) return false;
|
|
1217
|
+
if (typeof candidate.naturalWidth === "number" && candidate.naturalWidth === 0) return false;
|
|
1218
|
+
return true;
|
|
1219
|
+
}
|
|
1220
|
+
if (typeof candidate.readyState === "number") return candidate.readyState >= 2;
|
|
1221
|
+
return true;
|
|
1222
|
+
}
|
|
1213
1223
|
function createWebGLPointRenderer(canvas) {
|
|
1214
1224
|
const gl = canvas.getContext("webgl2", {
|
|
1215
1225
|
premultipliedAlpha: false
|
|
@@ -1445,6 +1455,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1445
1455
|
},
|
|
1446
1456
|
setTexture(source) {
|
|
1447
1457
|
if (source === textureSource && texture) return;
|
|
1458
|
+
if (!isSourceReady(source)) return;
|
|
1448
1459
|
if (!texture) {
|
|
1449
1460
|
texture = gl.createTexture();
|
|
1450
1461
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
@@ -1487,6 +1498,10 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1487
1498
|
distanceRange = range;
|
|
1488
1499
|
return;
|
|
1489
1500
|
}
|
|
1501
|
+
if (!isSourceReady(source)) {
|
|
1502
|
+
distanceRange = range;
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1490
1505
|
atlasSwitches++;
|
|
1491
1506
|
drawGlyphs();
|
|
1492
1507
|
distanceRange = range;
|
|
@@ -1212,6 +1212,16 @@ function grow(data, needed) {
|
|
|
1212
1212
|
grown.set(data);
|
|
1213
1213
|
return grown;
|
|
1214
1214
|
}
|
|
1215
|
+
function isSourceReady(source) {
|
|
1216
|
+
const candidate = source;
|
|
1217
|
+
if (typeof candidate.complete === "boolean") {
|
|
1218
|
+
if (!candidate.complete) return false;
|
|
1219
|
+
if (typeof candidate.naturalWidth === "number" && candidate.naturalWidth === 0) return false;
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
if (typeof candidate.readyState === "number") return candidate.readyState >= 2;
|
|
1223
|
+
return true;
|
|
1224
|
+
}
|
|
1215
1225
|
function createWebGLPointRenderer(canvas) {
|
|
1216
1226
|
const gl = canvas.getContext("webgl2", {
|
|
1217
1227
|
premultipliedAlpha: false
|
|
@@ -1447,6 +1457,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1447
1457
|
},
|
|
1448
1458
|
setTexture(source) {
|
|
1449
1459
|
if (source === textureSource && texture) return;
|
|
1460
|
+
if (!isSourceReady(source)) return;
|
|
1450
1461
|
if (!texture) {
|
|
1451
1462
|
texture = gl.createTexture();
|
|
1452
1463
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
@@ -1489,6 +1500,10 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1489
1500
|
distanceRange = range;
|
|
1490
1501
|
return;
|
|
1491
1502
|
}
|
|
1503
|
+
if (!isSourceReady(source)) {
|
|
1504
|
+
distanceRange = range;
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1492
1507
|
atlasSwitches++;
|
|
1493
1508
|
drawGlyphs();
|
|
1494
1509
|
distanceRange = range;
|
|
@@ -1093,6 +1093,11 @@ var MSDFTextEntity = class extends Entity {
|
|
|
1093
1093
|
layoutText = "";
|
|
1094
1094
|
text = "";
|
|
1095
1095
|
lastRenderedSeqId = 0;
|
|
1096
|
+
// Atlas-decode subscription (see watchAtlasDecode). Held so `destroy()` can
|
|
1097
|
+
// release it: the handler closes over `this`, so leaving it attached to a
|
|
1098
|
+
// long-lived shared atlas image would retain the whole entity.
|
|
1099
|
+
atlasDecodeTarget = null;
|
|
1100
|
+
atlasDecodeHandler = null;
|
|
1096
1101
|
rgbColorCache = /* @__PURE__ */ new Map();
|
|
1097
1102
|
fontStringCache = [];
|
|
1098
1103
|
layoutResult = null;
|
|
@@ -1108,8 +1113,53 @@ var MSDFTextEntity = class extends Entity {
|
|
|
1108
1113
|
this.maxWidth = options.maxWidth ?? 1e3;
|
|
1109
1114
|
this.maxHeight = options.maxHeight ?? 1e3;
|
|
1110
1115
|
this.textAlign = options.textAlign ?? "left";
|
|
1116
|
+
this.watchAtlasDecode();
|
|
1111
1117
|
this.setText(text);
|
|
1112
1118
|
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Repaint once the atlas raster decodes.
|
|
1121
|
+
*
|
|
1122
|
+
* The WebGL backend refuses to upload a not-yet-decoded atlas (it would pin an
|
|
1123
|
+
* empty texture in its identity cache forever), so the upload has to happen on
|
|
1124
|
+
* a LATER frame — and nothing else schedules one. Layout marks the scene dirty
|
|
1125
|
+
* when the worker replies, which for a network-served atlas is long before the
|
|
1126
|
+
* image lands, so the scene is already idle by then.
|
|
1127
|
+
*
|
|
1128
|
+
* Measured on Chromium and Firefox (2026-07-31) with a 600 ms atlas: the
|
|
1129
|
+
* scene's own rAF loop never uploaded a decoded atlas in EITHER render mode.
|
|
1130
|
+
* `onDemand` skips idle frames outright; `always` throttles to 2 FPS when
|
|
1131
|
+
* idle, so whether it recovers is down to whether a throttled tick happens to
|
|
1132
|
+
* land after the decode — Chromium got one, Firefox did not. Neither is a
|
|
1133
|
+
* mechanism, which is why this listener exists rather than relying on the
|
|
1134
|
+
* frame loop to come back around.
|
|
1135
|
+
*
|
|
1136
|
+
* Only `HTMLImageElement`-shaped sources have a decode to wait for; a canvas,
|
|
1137
|
+
* `ImageBitmap`, or `VideoFrame` atlas is ready on arrival.
|
|
1138
|
+
*/
|
|
1139
|
+
watchAtlasDecode() {
|
|
1140
|
+
const source = this.texture;
|
|
1141
|
+
if (typeof source.complete !== "boolean" || typeof source.addEventListener !== "function" || typeof source.removeEventListener !== "function") {
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
if (source.complete && typeof source.naturalWidth === "number" && source.naturalWidth > 0) {
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const target = this.texture;
|
|
1148
|
+
this.atlasDecodeHandler = () => {
|
|
1149
|
+
this.detachAtlasDecodeListener();
|
|
1150
|
+
this.scene?.markDirty();
|
|
1151
|
+
};
|
|
1152
|
+
target.addEventListener("load", this.atlasDecodeHandler);
|
|
1153
|
+
target.addEventListener("error", this.atlasDecodeHandler);
|
|
1154
|
+
this.atlasDecodeTarget = target;
|
|
1155
|
+
}
|
|
1156
|
+
detachAtlasDecodeListener() {
|
|
1157
|
+
if (!this.atlasDecodeTarget || !this.atlasDecodeHandler) return;
|
|
1158
|
+
this.atlasDecodeTarget.removeEventListener("load", this.atlasDecodeHandler);
|
|
1159
|
+
this.atlasDecodeTarget.removeEventListener("error", this.atlasDecodeHandler);
|
|
1160
|
+
this.atlasDecodeTarget = null;
|
|
1161
|
+
this.atlasDecodeHandler = null;
|
|
1162
|
+
}
|
|
1113
1163
|
/** Change the wrap boundary and re-run layout for the current text. */
|
|
1114
1164
|
setMaxWidth(maxWidth) {
|
|
1115
1165
|
if (this.maxWidth === maxWidth) return;
|
|
@@ -1281,11 +1331,13 @@ var MSDFTextEntity = class extends Entity {
|
|
|
1281
1331
|
}
|
|
1282
1332
|
destroy() {
|
|
1283
1333
|
LayoutWorkerManager.cancelLayoutForEntity(this.id);
|
|
1334
|
+
this.detachAtlasDecodeListener();
|
|
1284
1335
|
super.destroy();
|
|
1285
1336
|
}
|
|
1286
1337
|
};
|
|
1287
1338
|
|
|
1288
1339
|
// src/text/SVGEntity.ts
|
|
1340
|
+
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
1289
1341
|
function isSvgWhitespace(ch) {
|
|
1290
1342
|
return ch === " " || ch === " " || ch === "\n" || ch === "\r";
|
|
1291
1343
|
}
|
|
@@ -1317,12 +1369,21 @@ function readSvgAttribute(source, name) {
|
|
|
1317
1369
|
return null;
|
|
1318
1370
|
}
|
|
1319
1371
|
var SVGEntity = class extends Entity {
|
|
1372
|
+
/**
|
|
1373
|
+
* Stroke colour of the fallback marker drawn when the source cannot be
|
|
1374
|
+
* rasterized. Set to `'transparent'` to opt out and keep the box empty.
|
|
1375
|
+
* Default `'rgba(248,113,113,0.9)'`.
|
|
1376
|
+
*/
|
|
1377
|
+
fallbackStroke = "rgba(248,113,113,0.9)";
|
|
1378
|
+
/** Fill behind the fallback marker. Default `'rgba(248,113,113,0.12)'`. */
|
|
1379
|
+
fallbackFill = "rgba(248,113,113,0.12)";
|
|
1320
1380
|
svgSource = "";
|
|
1321
1381
|
imageBitmap = null;
|
|
1322
1382
|
imageElement = null;
|
|
1323
1383
|
blobURL = null;
|
|
1324
1384
|
currentImg = null;
|
|
1325
1385
|
lodTimeout = null;
|
|
1386
|
+
rasterFailed = false;
|
|
1326
1387
|
cachedDoc = null;
|
|
1327
1388
|
baseWidth = 100;
|
|
1328
1389
|
baseHeight = 100;
|
|
@@ -1349,6 +1410,7 @@ var SVGEntity = class extends Entity {
|
|
|
1349
1410
|
const parserError = doc.querySelector("parsererror");
|
|
1350
1411
|
if (parserError) {
|
|
1351
1412
|
console.error("SVG Parsing error:", parserError.textContent);
|
|
1413
|
+
this.rasterFailed = true;
|
|
1352
1414
|
} else {
|
|
1353
1415
|
this.cachedDoc = doc;
|
|
1354
1416
|
const svgEl = doc.documentElement;
|
|
@@ -1368,6 +1430,7 @@ var SVGEntity = class extends Entity {
|
|
|
1368
1430
|
}
|
|
1369
1431
|
} catch (e) {
|
|
1370
1432
|
console.error("Failed parsing SVG via DOMParser, falling back to attribute scan:", e);
|
|
1433
|
+
this.rasterFailed = true;
|
|
1371
1434
|
}
|
|
1372
1435
|
} else {
|
|
1373
1436
|
const wAttr = readSvgAttribute(this.svgSource, "width");
|
|
@@ -1391,6 +1454,7 @@ var SVGEntity = class extends Entity {
|
|
|
1391
1454
|
}
|
|
1392
1455
|
triggerRasterization(scale) {
|
|
1393
1456
|
if (typeof window === "undefined" || typeof Blob === "undefined") return;
|
|
1457
|
+
this.rasterFailed = false;
|
|
1394
1458
|
if (this.currentImg) {
|
|
1395
1459
|
this.currentImg.onload = null;
|
|
1396
1460
|
this.currentImg.onerror = null;
|
|
@@ -1414,6 +1478,7 @@ var SVGEntity = class extends Entity {
|
|
|
1414
1478
|
"SVG Parsing validation error in triggerRasterization:",
|
|
1415
1479
|
parserError.textContent
|
|
1416
1480
|
);
|
|
1481
|
+
this.rasterFailed = true;
|
|
1417
1482
|
} else {
|
|
1418
1483
|
const clonedDoc = doc.cloneNode(true);
|
|
1419
1484
|
const svgEl = clonedDoc.documentElement;
|
|
@@ -1427,10 +1492,14 @@ var SVGEntity = class extends Entity {
|
|
|
1427
1492
|
}
|
|
1428
1493
|
const serializer = new XMLSerializer();
|
|
1429
1494
|
processedSource = serializer.serializeToString(clonedDoc);
|
|
1495
|
+
if (svgEl.namespaceURI === null && !/\sxmlns\s*=/.test(processedSource)) {
|
|
1496
|
+
processedSource = processedSource.replace(/<svg/i, `<svg xmlns="${SVG_NAMESPACE}"`);
|
|
1497
|
+
}
|
|
1430
1498
|
}
|
|
1431
1499
|
}
|
|
1432
1500
|
} catch (e) {
|
|
1433
1501
|
console.error("Failed to apply LOD scaling to SVG XML:", e);
|
|
1502
|
+
this.rasterFailed = true;
|
|
1434
1503
|
}
|
|
1435
1504
|
const blob = new Blob([processedSource], { type: "image/svg+xml;charset=utf-8" });
|
|
1436
1505
|
this.blobURL = URL.createObjectURL(blob);
|
|
@@ -1459,15 +1528,36 @@ var SVGEntity = class extends Entity {
|
|
|
1459
1528
|
}).catch((e) => {
|
|
1460
1529
|
console.error("Failed to create ImageBitmap from SVG:", e);
|
|
1461
1530
|
this.currentImg = null;
|
|
1531
|
+
if (!this.imageElement) this.rasterFailed = true;
|
|
1532
|
+
if (this.scene) this.scene.markDirty();
|
|
1462
1533
|
});
|
|
1463
1534
|
};
|
|
1464
1535
|
img.onerror = (e) => {
|
|
1465
1536
|
if (this.currentImg !== img) return;
|
|
1466
1537
|
console.error("Failed to load SVG Image element:", e);
|
|
1467
1538
|
this.currentImg = null;
|
|
1539
|
+
this.rasterFailed = true;
|
|
1540
|
+
if (this.scene) this.scene.markDirty();
|
|
1468
1541
|
};
|
|
1469
1542
|
img.src = this.blobURL;
|
|
1470
1543
|
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Whether the source genuinely rasterized to a bitmap.
|
|
1546
|
+
*
|
|
1547
|
+
* Distinguishes "drew the real artwork" from "drew the fallback marker",
|
|
1548
|
+
* which pixel counts alone cannot tell apart — both are non-blank.
|
|
1549
|
+
*/
|
|
1550
|
+
hasRasterBitmap() {
|
|
1551
|
+
return this.imageBitmap !== null;
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Whether rasterization failed, so {@link render} draws the fallback marker.
|
|
1555
|
+
*
|
|
1556
|
+
* `false` while a raster is still in flight; only a settled failure sets it.
|
|
1557
|
+
*/
|
|
1558
|
+
hasRasterFailed() {
|
|
1559
|
+
return this.rasterFailed;
|
|
1560
|
+
}
|
|
1471
1561
|
isPointInside(globalX, globalY) {
|
|
1472
1562
|
const local = this.worldToLocal(globalX, globalY);
|
|
1473
1563
|
if (!local) return false;
|
|
@@ -1492,9 +1582,32 @@ var SVGEntity = class extends Entity {
|
|
|
1492
1582
|
}
|
|
1493
1583
|
if (this.imageBitmap) {
|
|
1494
1584
|
r.drawImage(this.imageBitmap, 0, 0, this.width, this.height);
|
|
1495
|
-
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
if (this.imageElement) {
|
|
1496
1588
|
r.drawImage(this.imageElement, 0, 0, this.width, this.height);
|
|
1589
|
+
return;
|
|
1497
1590
|
}
|
|
1591
|
+
if (this.rasterFailed) this.drawFallback(r);
|
|
1592
|
+
}
|
|
1593
|
+
/** Box outline plus a diagonal cross — the conventional "broken image" mark. */
|
|
1594
|
+
drawFallback(r) {
|
|
1595
|
+
const w = this.width;
|
|
1596
|
+
const h = this.height;
|
|
1597
|
+
if (w <= 0 || h <= 0) return;
|
|
1598
|
+
r.beginPath();
|
|
1599
|
+
r.roundRect(0, 0, w, h, 0);
|
|
1600
|
+
r.fill(this.fallbackFill);
|
|
1601
|
+
r.beginPath();
|
|
1602
|
+
r.roundRect(0, 0, w, h, 0);
|
|
1603
|
+
r.stroke(this.fallbackStroke, 1);
|
|
1604
|
+
const inset = Math.min(w, h) * 0.2;
|
|
1605
|
+
r.beginPath();
|
|
1606
|
+
r.moveTo(inset, inset);
|
|
1607
|
+
r.lineTo(w - inset, h - inset);
|
|
1608
|
+
r.moveTo(w - inset, inset);
|
|
1609
|
+
r.lineTo(inset, h - inset);
|
|
1610
|
+
r.stroke(this.fallbackStroke, 1);
|
|
1498
1611
|
}
|
|
1499
1612
|
destroy() {
|
|
1500
1613
|
if (this.lodTimeout) {
|
|
@@ -1095,11 +1095,16 @@ var MSDFTextEntity = (_class3 = class extends Entity {
|
|
|
1095
1095
|
__init40() {this.layoutText = ""}
|
|
1096
1096
|
__init41() {this.text = ""}
|
|
1097
1097
|
__init42() {this.lastRenderedSeqId = 0}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1098
|
+
// Atlas-decode subscription (see watchAtlasDecode). Held so `destroy()` can
|
|
1099
|
+
// release it: the handler closes over `this`, so leaving it attached to a
|
|
1100
|
+
// long-lived shared atlas image would retain the whole entity.
|
|
1101
|
+
__init43() {this.atlasDecodeTarget = null}
|
|
1102
|
+
__init44() {this.atlasDecodeHandler = null}
|
|
1103
|
+
__init45() {this.rgbColorCache = /* @__PURE__ */ new Map()}
|
|
1104
|
+
__init46() {this.fontStringCache = []}
|
|
1105
|
+
__init47() {this.layoutResult = null}
|
|
1101
1106
|
constructor(text, options) {
|
|
1102
|
-
super();_class3.prototype.__init39.call(this);_class3.prototype.__init40.call(this);_class3.prototype.__init41.call(this);_class3.prototype.__init42.call(this);_class3.prototype.__init43.call(this);_class3.prototype.__init44.call(this);_class3.prototype.__init45.call(this);;
|
|
1107
|
+
super();_class3.prototype.__init39.call(this);_class3.prototype.__init40.call(this);_class3.prototype.__init41.call(this);_class3.prototype.__init42.call(this);_class3.prototype.__init43.call(this);_class3.prototype.__init44.call(this);_class3.prototype.__init45.call(this);_class3.prototype.__init46.call(this);_class3.prototype.__init47.call(this);;
|
|
1103
1108
|
this.font = options.font;
|
|
1104
1109
|
this.texture = options.texture;
|
|
1105
1110
|
this.fallbackFont = _nullishCoalesce(options.fallbackFont, () => ( "sans-serif"));
|
|
@@ -1110,8 +1115,53 @@ var MSDFTextEntity = (_class3 = class extends Entity {
|
|
|
1110
1115
|
this.maxWidth = _nullishCoalesce(options.maxWidth, () => ( 1e3));
|
|
1111
1116
|
this.maxHeight = _nullishCoalesce(options.maxHeight, () => ( 1e3));
|
|
1112
1117
|
this.textAlign = _nullishCoalesce(options.textAlign, () => ( "left"));
|
|
1118
|
+
this.watchAtlasDecode();
|
|
1113
1119
|
this.setText(text);
|
|
1114
1120
|
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Repaint once the atlas raster decodes.
|
|
1123
|
+
*
|
|
1124
|
+
* The WebGL backend refuses to upload a not-yet-decoded atlas (it would pin an
|
|
1125
|
+
* empty texture in its identity cache forever), so the upload has to happen on
|
|
1126
|
+
* a LATER frame — and nothing else schedules one. Layout marks the scene dirty
|
|
1127
|
+
* when the worker replies, which for a network-served atlas is long before the
|
|
1128
|
+
* image lands, so the scene is already idle by then.
|
|
1129
|
+
*
|
|
1130
|
+
* Measured on Chromium and Firefox (2026-07-31) with a 600 ms atlas: the
|
|
1131
|
+
* scene's own rAF loop never uploaded a decoded atlas in EITHER render mode.
|
|
1132
|
+
* `onDemand` skips idle frames outright; `always` throttles to 2 FPS when
|
|
1133
|
+
* idle, so whether it recovers is down to whether a throttled tick happens to
|
|
1134
|
+
* land after the decode — Chromium got one, Firefox did not. Neither is a
|
|
1135
|
+
* mechanism, which is why this listener exists rather than relying on the
|
|
1136
|
+
* frame loop to come back around.
|
|
1137
|
+
*
|
|
1138
|
+
* Only `HTMLImageElement`-shaped sources have a decode to wait for; a canvas,
|
|
1139
|
+
* `ImageBitmap`, or `VideoFrame` atlas is ready on arrival.
|
|
1140
|
+
*/
|
|
1141
|
+
watchAtlasDecode() {
|
|
1142
|
+
const source = this.texture;
|
|
1143
|
+
if (typeof source.complete !== "boolean" || typeof source.addEventListener !== "function" || typeof source.removeEventListener !== "function") {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (source.complete && typeof source.naturalWidth === "number" && source.naturalWidth > 0) {
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
const target = this.texture;
|
|
1150
|
+
this.atlasDecodeHandler = () => {
|
|
1151
|
+
this.detachAtlasDecodeListener();
|
|
1152
|
+
_optionalChain([this, 'access', _108 => _108.scene, 'optionalAccess', _109 => _109.markDirty, 'call', _110 => _110()]);
|
|
1153
|
+
};
|
|
1154
|
+
target.addEventListener("load", this.atlasDecodeHandler);
|
|
1155
|
+
target.addEventListener("error", this.atlasDecodeHandler);
|
|
1156
|
+
this.atlasDecodeTarget = target;
|
|
1157
|
+
}
|
|
1158
|
+
detachAtlasDecodeListener() {
|
|
1159
|
+
if (!this.atlasDecodeTarget || !this.atlasDecodeHandler) return;
|
|
1160
|
+
this.atlasDecodeTarget.removeEventListener("load", this.atlasDecodeHandler);
|
|
1161
|
+
this.atlasDecodeTarget.removeEventListener("error", this.atlasDecodeHandler);
|
|
1162
|
+
this.atlasDecodeTarget = null;
|
|
1163
|
+
this.atlasDecodeHandler = null;
|
|
1164
|
+
}
|
|
1115
1165
|
/** Change the wrap boundary and re-run layout for the current text. */
|
|
1116
1166
|
setMaxWidth(maxWidth) {
|
|
1117
1167
|
if (this.maxWidth === maxWidth) return;
|
|
@@ -1177,7 +1227,7 @@ var MSDFTextEntity = (_class3 = class extends Entity {
|
|
|
1177
1227
|
if (res.seqId < this.lastRenderedSeqId) return;
|
|
1178
1228
|
this.lastRenderedSeqId = res.seqId;
|
|
1179
1229
|
this.layoutResult = res;
|
|
1180
|
-
_optionalChain([this, 'access',
|
|
1230
|
+
_optionalChain([this, 'access', _111 => _111.scene, 'optionalAccess', _112 => _112.markDirty, 'call', _113 => _113()]);
|
|
1181
1231
|
}
|
|
1182
1232
|
});
|
|
1183
1233
|
}
|
|
@@ -1283,11 +1333,13 @@ var MSDFTextEntity = (_class3 = class extends Entity {
|
|
|
1283
1333
|
}
|
|
1284
1334
|
destroy() {
|
|
1285
1335
|
_layout.LayoutWorkerManager.cancelLayoutForEntity(this.id);
|
|
1336
|
+
this.detachAtlasDecodeListener();
|
|
1286
1337
|
super.destroy();
|
|
1287
1338
|
}
|
|
1288
1339
|
}, _class3);
|
|
1289
1340
|
|
|
1290
1341
|
// src/text/SVGEntity.ts
|
|
1342
|
+
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
1291
1343
|
function isSvgWhitespace(ch) {
|
|
1292
1344
|
return ch === " " || ch === " " || ch === "\n" || ch === "\r";
|
|
1293
1345
|
}
|
|
@@ -1319,19 +1371,28 @@ function readSvgAttribute(source, name) {
|
|
|
1319
1371
|
return null;
|
|
1320
1372
|
}
|
|
1321
1373
|
var SVGEntity = (_class4 = class extends Entity {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1374
|
+
/**
|
|
1375
|
+
* Stroke colour of the fallback marker drawn when the source cannot be
|
|
1376
|
+
* rasterized. Set to `'transparent'` to opt out and keep the box empty.
|
|
1377
|
+
* Default `'rgba(248,113,113,0.9)'`.
|
|
1378
|
+
*/
|
|
1379
|
+
__init48() {this.fallbackStroke = "rgba(248,113,113,0.9)"}
|
|
1380
|
+
/** Fill behind the fallback marker. Default `'rgba(248,113,113,0.12)'`. */
|
|
1381
|
+
__init49() {this.fallbackFill = "rgba(248,113,113,0.12)"}
|
|
1382
|
+
__init50() {this.svgSource = ""}
|
|
1383
|
+
__init51() {this.imageBitmap = null}
|
|
1384
|
+
__init52() {this.imageElement = null}
|
|
1385
|
+
__init53() {this.blobURL = null}
|
|
1386
|
+
__init54() {this.currentImg = null}
|
|
1387
|
+
__init55() {this.lodTimeout = null}
|
|
1388
|
+
__init56() {this.rasterFailed = false}
|
|
1389
|
+
__init57() {this.cachedDoc = null}
|
|
1390
|
+
__init58() {this.baseWidth = 100}
|
|
1391
|
+
__init59() {this.baseHeight = 100}
|
|
1392
|
+
__init60() {this.lastRasterizedScale = 1}
|
|
1393
|
+
__init61() {this.targetScale = 1}
|
|
1333
1394
|
constructor(svgSource, id) {
|
|
1334
|
-
super(id);_class4.prototype.
|
|
1395
|
+
super(id);_class4.prototype.__init48.call(this);_class4.prototype.__init49.call(this);_class4.prototype.__init50.call(this);_class4.prototype.__init51.call(this);_class4.prototype.__init52.call(this);_class4.prototype.__init53.call(this);_class4.prototype.__init54.call(this);_class4.prototype.__init55.call(this);_class4.prototype.__init56.call(this);_class4.prototype.__init57.call(this);_class4.prototype.__init58.call(this);_class4.prototype.__init59.call(this);_class4.prototype.__init60.call(this);_class4.prototype.__init61.call(this);;
|
|
1335
1396
|
this.setSVGSource(svgSource);
|
|
1336
1397
|
}
|
|
1337
1398
|
setSVGSource(svgSource) {
|
|
@@ -1351,6 +1412,7 @@ var SVGEntity = (_class4 = class extends Entity {
|
|
|
1351
1412
|
const parserError = doc.querySelector("parsererror");
|
|
1352
1413
|
if (parserError) {
|
|
1353
1414
|
console.error("SVG Parsing error:", parserError.textContent);
|
|
1415
|
+
this.rasterFailed = true;
|
|
1354
1416
|
} else {
|
|
1355
1417
|
this.cachedDoc = doc;
|
|
1356
1418
|
const svgEl = doc.documentElement;
|
|
@@ -1370,6 +1432,7 @@ var SVGEntity = (_class4 = class extends Entity {
|
|
|
1370
1432
|
}
|
|
1371
1433
|
} catch (e) {
|
|
1372
1434
|
console.error("Failed parsing SVG via DOMParser, falling back to attribute scan:", e);
|
|
1435
|
+
this.rasterFailed = true;
|
|
1373
1436
|
}
|
|
1374
1437
|
} else {
|
|
1375
1438
|
const wAttr = readSvgAttribute(this.svgSource, "width");
|
|
@@ -1393,6 +1456,7 @@ var SVGEntity = (_class4 = class extends Entity {
|
|
|
1393
1456
|
}
|
|
1394
1457
|
triggerRasterization(scale) {
|
|
1395
1458
|
if (typeof window === "undefined" || typeof Blob === "undefined") return;
|
|
1459
|
+
this.rasterFailed = false;
|
|
1396
1460
|
if (this.currentImg) {
|
|
1397
1461
|
this.currentImg.onload = null;
|
|
1398
1462
|
this.currentImg.onerror = null;
|
|
@@ -1416,6 +1480,7 @@ var SVGEntity = (_class4 = class extends Entity {
|
|
|
1416
1480
|
"SVG Parsing validation error in triggerRasterization:",
|
|
1417
1481
|
parserError.textContent
|
|
1418
1482
|
);
|
|
1483
|
+
this.rasterFailed = true;
|
|
1419
1484
|
} else {
|
|
1420
1485
|
const clonedDoc = doc.cloneNode(true);
|
|
1421
1486
|
const svgEl = clonedDoc.documentElement;
|
|
@@ -1429,10 +1494,14 @@ var SVGEntity = (_class4 = class extends Entity {
|
|
|
1429
1494
|
}
|
|
1430
1495
|
const serializer = new XMLSerializer();
|
|
1431
1496
|
processedSource = serializer.serializeToString(clonedDoc);
|
|
1497
|
+
if (svgEl.namespaceURI === null && !/\sxmlns\s*=/.test(processedSource)) {
|
|
1498
|
+
processedSource = processedSource.replace(/<svg/i, `<svg xmlns="${SVG_NAMESPACE}"`);
|
|
1499
|
+
}
|
|
1432
1500
|
}
|
|
1433
1501
|
}
|
|
1434
1502
|
} catch (e) {
|
|
1435
1503
|
console.error("Failed to apply LOD scaling to SVG XML:", e);
|
|
1504
|
+
this.rasterFailed = true;
|
|
1436
1505
|
}
|
|
1437
1506
|
const blob = new Blob([processedSource], { type: "image/svg+xml;charset=utf-8" });
|
|
1438
1507
|
this.blobURL = URL.createObjectURL(blob);
|
|
@@ -1461,15 +1530,36 @@ var SVGEntity = (_class4 = class extends Entity {
|
|
|
1461
1530
|
}).catch((e) => {
|
|
1462
1531
|
console.error("Failed to create ImageBitmap from SVG:", e);
|
|
1463
1532
|
this.currentImg = null;
|
|
1533
|
+
if (!this.imageElement) this.rasterFailed = true;
|
|
1534
|
+
if (this.scene) this.scene.markDirty();
|
|
1464
1535
|
});
|
|
1465
1536
|
};
|
|
1466
1537
|
img.onerror = (e) => {
|
|
1467
1538
|
if (this.currentImg !== img) return;
|
|
1468
1539
|
console.error("Failed to load SVG Image element:", e);
|
|
1469
1540
|
this.currentImg = null;
|
|
1541
|
+
this.rasterFailed = true;
|
|
1542
|
+
if (this.scene) this.scene.markDirty();
|
|
1470
1543
|
};
|
|
1471
1544
|
img.src = this.blobURL;
|
|
1472
1545
|
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Whether the source genuinely rasterized to a bitmap.
|
|
1548
|
+
*
|
|
1549
|
+
* Distinguishes "drew the real artwork" from "drew the fallback marker",
|
|
1550
|
+
* which pixel counts alone cannot tell apart — both are non-blank.
|
|
1551
|
+
*/
|
|
1552
|
+
hasRasterBitmap() {
|
|
1553
|
+
return this.imageBitmap !== null;
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Whether rasterization failed, so {@link render} draws the fallback marker.
|
|
1557
|
+
*
|
|
1558
|
+
* `false` while a raster is still in flight; only a settled failure sets it.
|
|
1559
|
+
*/
|
|
1560
|
+
hasRasterFailed() {
|
|
1561
|
+
return this.rasterFailed;
|
|
1562
|
+
}
|
|
1473
1563
|
isPointInside(globalX, globalY) {
|
|
1474
1564
|
const local = this.worldToLocal(globalX, globalY);
|
|
1475
1565
|
if (!local) return false;
|
|
@@ -1494,9 +1584,32 @@ var SVGEntity = (_class4 = class extends Entity {
|
|
|
1494
1584
|
}
|
|
1495
1585
|
if (this.imageBitmap) {
|
|
1496
1586
|
r.drawImage(this.imageBitmap, 0, 0, this.width, this.height);
|
|
1497
|
-
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
if (this.imageElement) {
|
|
1498
1590
|
r.drawImage(this.imageElement, 0, 0, this.width, this.height);
|
|
1591
|
+
return;
|
|
1499
1592
|
}
|
|
1593
|
+
if (this.rasterFailed) this.drawFallback(r);
|
|
1594
|
+
}
|
|
1595
|
+
/** Box outline plus a diagonal cross — the conventional "broken image" mark. */
|
|
1596
|
+
drawFallback(r) {
|
|
1597
|
+
const w = this.width;
|
|
1598
|
+
const h = this.height;
|
|
1599
|
+
if (w <= 0 || h <= 0) return;
|
|
1600
|
+
r.beginPath();
|
|
1601
|
+
r.roundRect(0, 0, w, h, 0);
|
|
1602
|
+
r.fill(this.fallbackFill);
|
|
1603
|
+
r.beginPath();
|
|
1604
|
+
r.roundRect(0, 0, w, h, 0);
|
|
1605
|
+
r.stroke(this.fallbackStroke, 1);
|
|
1606
|
+
const inset = Math.min(w, h) * 0.2;
|
|
1607
|
+
r.beginPath();
|
|
1608
|
+
r.moveTo(inset, inset);
|
|
1609
|
+
r.lineTo(w - inset, h - inset);
|
|
1610
|
+
r.moveTo(w - inset, inset);
|
|
1611
|
+
r.lineTo(inset, h - inset);
|
|
1612
|
+
r.stroke(this.fallbackStroke, 1);
|
|
1500
1613
|
}
|
|
1501
1614
|
destroy() {
|
|
1502
1615
|
if (this.lodTimeout) {
|
package/dist/index.js
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
var
|
|
12
|
+
var _chunkKHGHP2J3js = require('./chunk-KHGHP2J3.js');
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
var
|
|
18
|
+
var _chunkZ3HFY75Rjs = require('./chunk-Z3HFY75R.js');
|
|
19
19
|
|
|
20
20
|
// src/tree/Scene.ts
|
|
21
21
|
var _animation = require('@vectojs/animation'); _createStarExport(_animation);
|
|
@@ -30,7 +30,7 @@ var PARTICLE_OFFSET_ORIGIN_X = 4;
|
|
|
30
30
|
var PARTICLE_OFFSET_ORIGIN_Y = 5;
|
|
31
31
|
var PARTICLE_OFFSET_SIZE = 6;
|
|
32
32
|
var PARTICLE_OFFSET_LIFE = 7;
|
|
33
|
-
var ComputeParticleEntity = (_class = class extends
|
|
33
|
+
var ComputeParticleEntity = (_class = class extends _chunkZ3HFY75Rjs.Entity {
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
|
|
@@ -2875,7 +2875,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
2875
2875
|
this.forcedColorsChangeHandler = () => this.markDirty();
|
|
2876
2876
|
_optionalChain([this, 'access', _58 => _58.forcedColorsQuery, 'access', _59 => _59.addEventListener, 'optionalCall', _60 => _60("change", this.forcedColorsChangeHandler)]);
|
|
2877
2877
|
}
|
|
2878
|
-
this.root = new class RootEntity extends
|
|
2878
|
+
this.root = new class RootEntity extends _chunkZ3HFY75Rjs.Entity {
|
|
2879
2879
|
isPointInside() {
|
|
2880
2880
|
return false;
|
|
2881
2881
|
}
|
|
@@ -2884,7 +2884,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
2884
2884
|
}
|
|
2885
2885
|
}("root");
|
|
2886
2886
|
this.root._scene = this;
|
|
2887
|
-
this.overlayRoot = new class OverlayRoot extends
|
|
2887
|
+
this.overlayRoot = new class OverlayRoot extends _chunkZ3HFY75Rjs.Entity {
|
|
2888
2888
|
isPointInside() {
|
|
2889
2889
|
return false;
|
|
2890
2890
|
}
|
|
@@ -2895,7 +2895,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
2895
2895
|
if (options.renderer) {
|
|
2896
2896
|
this.renderer = options.renderer;
|
|
2897
2897
|
} else {
|
|
2898
|
-
this.renderer = new (0,
|
|
2898
|
+
this.renderer = new (0, _chunkKHGHP2J3js.CanvasRenderer)(
|
|
2899
2899
|
canvas,
|
|
2900
2900
|
this.disableWindowResize ? { width: this.width, height: this.height } : void 0,
|
|
2901
2901
|
this.maxDPR
|
|
@@ -3299,7 +3299,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
3299
3299
|
}
|
|
3300
3300
|
if (this.hoveredA11yElements.has(el)) {
|
|
3301
3301
|
this.hoveredA11yElements.delete(el);
|
|
3302
|
-
node.dispatchEvent(new (0,
|
|
3302
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("pointerleave", node, void 0, false));
|
|
3303
3303
|
}
|
|
3304
3304
|
this.preserveFocusOnRemoval(el);
|
|
3305
3305
|
el.remove();
|
|
@@ -3812,20 +3812,20 @@ var Scene = (_class7 = class _Scene {
|
|
|
3812
3812
|
el.style.background = "transparent";
|
|
3813
3813
|
}
|
|
3814
3814
|
el.addEventListener("click", (e) => {
|
|
3815
|
-
node.dispatchEvent(new (0,
|
|
3815
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("click", node, e));
|
|
3816
3816
|
});
|
|
3817
3817
|
el.addEventListener("dblclick", (e) => {
|
|
3818
|
-
node.dispatchEvent(new (0,
|
|
3818
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("dblclick", node, e));
|
|
3819
3819
|
});
|
|
3820
3820
|
el.addEventListener("mouseenter", (e) => {
|
|
3821
3821
|
if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
|
|
3822
3822
|
this.hoveredA11yElements.add(el);
|
|
3823
|
-
node.dispatchEvent(new (0,
|
|
3823
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("hover", node, e, false));
|
|
3824
3824
|
});
|
|
3825
3825
|
el.addEventListener("mouseleave", (e) => {
|
|
3826
3826
|
if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
|
|
3827
3827
|
this.hoveredA11yElements.delete(el);
|
|
3828
|
-
node.dispatchEvent(new (0,
|
|
3828
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("pointerleave", node, e, false));
|
|
3829
3829
|
});
|
|
3830
3830
|
const capEl = el;
|
|
3831
3831
|
const releasePointer = (event) => {
|
|
@@ -3841,32 +3841,32 @@ var Scene = (_class7 = class _Scene {
|
|
|
3841
3841
|
};
|
|
3842
3842
|
el.addEventListener("pointerdown", (e) => {
|
|
3843
3843
|
if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
|
|
3844
|
-
node.dispatchEvent(new (0,
|
|
3844
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("pointerdown", node, e));
|
|
3845
3845
|
});
|
|
3846
3846
|
el.addEventListener("pointerup", (e) => {
|
|
3847
3847
|
releasePointer(e);
|
|
3848
|
-
node.dispatchEvent(new (0,
|
|
3848
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("pointerup", node, e));
|
|
3849
3849
|
});
|
|
3850
3850
|
el.addEventListener("pointercancel", (e) => {
|
|
3851
3851
|
releasePointer(e);
|
|
3852
|
-
node.dispatchEvent(new (0,
|
|
3852
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("pointercancel", node, e));
|
|
3853
3853
|
});
|
|
3854
3854
|
el.addEventListener(
|
|
3855
3855
|
"pointermove",
|
|
3856
|
-
(e) => node.dispatchEvent(new (0,
|
|
3856
|
+
(e) => node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("pointermove", node, e))
|
|
3857
3857
|
);
|
|
3858
3858
|
el.addEventListener(
|
|
3859
3859
|
"wheel",
|
|
3860
3860
|
(e) => {
|
|
3861
|
-
node.dispatchEvent(new (0,
|
|
3861
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("wheel", node, e));
|
|
3862
3862
|
},
|
|
3863
3863
|
{ passive: false }
|
|
3864
3864
|
);
|
|
3865
3865
|
el.addEventListener("keydown", (e) => {
|
|
3866
|
-
node.dispatchEvent(new (0,
|
|
3866
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("keydown", node, e));
|
|
3867
3867
|
});
|
|
3868
3868
|
el.addEventListener("keyup", (e) => {
|
|
3869
|
-
node.dispatchEvent(new (0,
|
|
3869
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("keyup", node, e));
|
|
3870
3870
|
});
|
|
3871
3871
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
3872
3872
|
const input = el;
|
|
@@ -3932,7 +3932,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
3932
3932
|
el.addEventListener("keydown", (e) => {
|
|
3933
3933
|
if (e.key === "Enter" || e.key === " ") {
|
|
3934
3934
|
e.preventDefault();
|
|
3935
|
-
node.dispatchEvent(new (0,
|
|
3935
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("click", node, e));
|
|
3936
3936
|
}
|
|
3937
3937
|
});
|
|
3938
3938
|
}
|
|
@@ -3970,7 +3970,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
3970
3970
|
this.syncOptionalAttribute(
|
|
3971
3971
|
el,
|
|
3972
3972
|
"href",
|
|
3973
|
-
attrs.href === void 0 ? void 0 :
|
|
3973
|
+
attrs.href === void 0 ? void 0 : _chunkKHGHP2J3js.sanitizeUrl.call(void 0, attrs.href)
|
|
3974
3974
|
);
|
|
3975
3975
|
this.syncOptionalAttribute(el, "target", attrs.target);
|
|
3976
3976
|
}
|
|
@@ -4247,7 +4247,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
4247
4247
|
el.addEventListener(
|
|
4248
4248
|
"wheel",
|
|
4249
4249
|
(e2) => {
|
|
4250
|
-
node.dispatchEvent(new (0,
|
|
4250
|
+
node.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)("wheel", node, e2));
|
|
4251
4251
|
},
|
|
4252
4252
|
{ passive: false }
|
|
4253
4253
|
);
|
|
@@ -5184,7 +5184,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
5184
5184
|
let walkHadAnimation = false;
|
|
5185
5185
|
let walkHadInteractive = false;
|
|
5186
5186
|
const runUpdate = (node) => {
|
|
5187
|
-
const overridesUpdate = node.update !==
|
|
5187
|
+
const overridesUpdate = node.update !== _chunkZ3HFY75Rjs.Entity.prototype.update;
|
|
5188
5188
|
let pending = node.hasPendingAnimations();
|
|
5189
5189
|
if (pending || overridesUpdate) {
|
|
5190
5190
|
node.update(dt, time);
|
|
@@ -5193,7 +5193,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
5193
5193
|
if (pending) walkHadAnimation = true;
|
|
5194
5194
|
if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
|
|
5195
5195
|
if (this._devActive && this._devFrameCount % 120 === 0) {
|
|
5196
|
-
if (overridesUpdate && node.hasPendingAnimations ===
|
|
5196
|
+
if (overridesUpdate && node.hasPendingAnimations === _chunkZ3HFY75Rjs.Entity.prototype.hasPendingAnimations) {
|
|
5197
5197
|
this._devWarn(
|
|
5198
5198
|
`Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to drop the animation to ~2fps. Override hasPendingAnimations() to return true while motion is in flight.`
|
|
5199
5199
|
);
|
|
@@ -5405,7 +5405,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
5405
5405
|
* Export the current scene state to a lightweight, flat SVG XML string.
|
|
5406
5406
|
*/
|
|
5407
5407
|
toSVG() {
|
|
5408
|
-
const renderer = new (0,
|
|
5408
|
+
const renderer = new (0, _chunkKHGHP2J3js.SVGRenderer)(this.width, this.height);
|
|
5409
5409
|
this.render(renderer, 0, 0);
|
|
5410
5410
|
return renderer.toXMLString();
|
|
5411
5411
|
}
|
|
@@ -5712,12 +5712,12 @@ function contentGridLineSignature(grid, line, projected, lineHeight, baseline, f
|
|
|
5712
5712
|
|
|
5713
5713
|
|
|
5714
5714
|
var _layout = require('@vectojs/layout'); _createStarExport(_layout);
|
|
5715
|
-
var sharedMeasurer;
|
|
5715
|
+
var sharedMeasurer = null;
|
|
5716
5716
|
function defaultMeasurer() {
|
|
5717
|
-
|
|
5717
|
+
sharedMeasurer ??= _layout.resolveGlyphMeasurer.call(void 0, "sans-serif");
|
|
5718
5718
|
return sharedMeasurer;
|
|
5719
5719
|
}
|
|
5720
|
-
var TextEntity = (_class8 = class extends
|
|
5720
|
+
var TextEntity = (_class8 = class extends _chunkZ3HFY75Rjs.Entity {
|
|
5721
5721
|
|
|
5722
5722
|
|
|
5723
5723
|
|
|
@@ -5843,7 +5843,7 @@ var TextEntity = (_class8 = class extends _chunkAGP4VLF4js.Entity {
|
|
|
5843
5843
|
}, _class8);
|
|
5844
5844
|
|
|
5845
5845
|
// src/components/GridTextEntity.ts
|
|
5846
|
-
var GridTextEntity = (_class9 = class extends
|
|
5846
|
+
var GridTextEntity = (_class9 = class extends _chunkZ3HFY75Rjs.Entity {
|
|
5847
5847
|
|
|
5848
5848
|
__init155() {this.fillStyle = "#ffffff"}
|
|
5849
5849
|
__init156() {this.grid = []}
|
|
@@ -5937,7 +5937,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
|
|
|
5937
5937
|
const ey = py - cy;
|
|
5938
5938
|
return ex * ex + ey * ey;
|
|
5939
5939
|
}
|
|
5940
|
-
var SplineEntity = (_class10 = class extends
|
|
5940
|
+
var SplineEntity = (_class10 = class extends _chunkZ3HFY75Rjs.Entity {
|
|
5941
5941
|
|
|
5942
5942
|
|
|
5943
5943
|
|
|
@@ -6212,7 +6212,7 @@ async function loadSpline(url) {
|
|
|
6212
6212
|
}
|
|
6213
6213
|
|
|
6214
6214
|
// src/components/Rect.ts
|
|
6215
|
-
var Rect = class extends
|
|
6215
|
+
var Rect = class extends _chunkZ3HFY75Rjs.Entity {
|
|
6216
6216
|
|
|
6217
6217
|
|
|
6218
6218
|
|
|
@@ -6261,7 +6261,7 @@ var Rect = class extends _chunkAGP4VLF4js.Entity {
|
|
|
6261
6261
|
};
|
|
6262
6262
|
|
|
6263
6263
|
// src/components/Circle.ts
|
|
6264
|
-
var Circle = class extends
|
|
6264
|
+
var Circle = class extends _chunkZ3HFY75Rjs.Entity {
|
|
6265
6265
|
|
|
6266
6266
|
|
|
6267
6267
|
|
|
@@ -6321,7 +6321,7 @@ var Circle = class extends _chunkAGP4VLF4js.Entity {
|
|
|
6321
6321
|
};
|
|
6322
6322
|
|
|
6323
6323
|
// src/components/Group.ts
|
|
6324
|
-
var Group = class extends
|
|
6324
|
+
var Group = class extends _chunkZ3HFY75Rjs.Entity {
|
|
6325
6325
|
constructor(...children) {
|
|
6326
6326
|
super();
|
|
6327
6327
|
if (children.length > 0) this.add(...children);
|
|
@@ -6340,7 +6340,7 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
|
|
|
6340
6340
|
|
|
6341
6341
|
|
|
6342
6342
|
// src/tree/DOMPortalEntity.ts
|
|
6343
|
-
var DOMPortalEntity = (_class11 = class extends
|
|
6343
|
+
var DOMPortalEntity = (_class11 = class extends _chunkZ3HFY75Rjs.Entity {
|
|
6344
6344
|
|
|
6345
6345
|
__init165() {this.isDOMPortal = true}
|
|
6346
6346
|
__init166() {this.domListeners = []}
|
|
@@ -6396,7 +6396,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkAGP4VLF4js.Entity {
|
|
|
6396
6396
|
];
|
|
6397
6397
|
for (const type of events) {
|
|
6398
6398
|
const handler = (e) => {
|
|
6399
|
-
this.dispatchEvent(new (0,
|
|
6399
|
+
this.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)(type, this, e));
|
|
6400
6400
|
};
|
|
6401
6401
|
this.domElement.addEventListener(type, handler);
|
|
6402
6402
|
this.domListeners.push({ type, handler, capture: false });
|
|
@@ -6407,7 +6407,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkAGP4VLF4js.Entity {
|
|
|
6407
6407
|
];
|
|
6408
6408
|
for (const { native, vecto } of hoverEvents) {
|
|
6409
6409
|
const handler = (e) => {
|
|
6410
|
-
this.dispatchEvent(new (0,
|
|
6410
|
+
this.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)(vecto, this, e, false));
|
|
6411
6411
|
};
|
|
6412
6412
|
this.domElement.addEventListener(native, handler);
|
|
6413
6413
|
this.domListeners.push({ type: native, handler, capture: false });
|
|
@@ -6415,7 +6415,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkAGP4VLF4js.Entity {
|
|
|
6415
6415
|
const focusEvents = ["focus", "blur"];
|
|
6416
6416
|
for (const type of focusEvents) {
|
|
6417
6417
|
const handler = (e) => {
|
|
6418
|
-
this.dispatchEvent(new (0,
|
|
6418
|
+
this.dispatchEvent(new (0, _chunkZ3HFY75Rjs.VectoJSEvent)(type, this, e, true));
|
|
6419
6419
|
};
|
|
6420
6420
|
this.domElement.addEventListener(type, handler, true);
|
|
6421
6421
|
this.domListeners.push({ type, handler, capture: true });
|
|
@@ -6466,8 +6466,8 @@ var DOMPortalEntity = (_class11 = class extends _chunkAGP4VLF4js.Entity {
|
|
|
6466
6466
|
}, _class11);
|
|
6467
6467
|
|
|
6468
6468
|
// src/index.ts
|
|
6469
|
-
Scene.registerWebGLPointRendererCreator(
|
|
6470
|
-
Scene.registerWebGPUParticleSystemManager(
|
|
6469
|
+
Scene.registerWebGLPointRendererCreator(_chunkKHGHP2J3js.createWebGLPointRenderer);
|
|
6470
|
+
Scene.registerWebGPUParticleSystemManager(_chunkKHGHP2J3js.WebGPUParticleSystemManager);
|
|
6471
6471
|
|
|
6472
6472
|
|
|
6473
6473
|
|
|
@@ -6507,4 +6507,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkKEBYJVD6js.WebGPUParticleSystemM
|
|
|
6507
6507
|
|
|
6508
6508
|
|
|
6509
6509
|
|
|
6510
|
-
exports.CanvasRenderer =
|
|
6510
|
+
exports.CanvasRenderer = _chunkKHGHP2J3js.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkZ3HFY75Rjs.Entity; exports.GlyphRasterAtlas = _chunkKHGHP2J3js.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkZ3HFY75Rjs.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SVGEntity = _chunkZ3HFY75Rjs.SVGEntity; exports.SVGRenderer = _chunkKHGHP2J3js.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkKHGHP2J3js.TextRasterCache; exports.VECTO_USER_TIMING = VECTO_USER_TIMING; exports.VectoJSEvent = _chunkZ3HFY75Rjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkKHGHP2J3js.WebGPUParticleSystemManager; exports.beginVectoUserTiming = beginVectoUserTiming; exports.createWebGLPointRenderer = _chunkKHGHP2J3js.createWebGLPointRenderer; exports.endVectoUserTiming = endVectoUserTiming; exports.isSafeUrl = _chunkKHGHP2J3js.isSafeUrl; exports.loadSpline = loadSpline; exports.measureVectoUserTiming = measureVectoUserTiming; exports.parseColorToRGBA = _chunkKHGHP2J3js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkKHGHP2J3js.sanitizeUrl;
|
package/dist/index.mjs
CHANGED
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
isSafeUrl,
|
|
9
9
|
parseColorToRGBA,
|
|
10
10
|
sanitizeUrl
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-I4WQY7CJ.mjs";
|
|
12
12
|
import {
|
|
13
13
|
Entity,
|
|
14
14
|
MSDFTextEntity,
|
|
15
15
|
SVGEntity,
|
|
16
16
|
VectoJSEvent
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-MVFPN4Y5.mjs";
|
|
18
18
|
|
|
19
19
|
// src/tree/Scene.ts
|
|
20
20
|
import { SpringDriver, TweenDriver } from "@vectojs/animation";
|
|
@@ -5709,11 +5709,11 @@ function contentGridLineSignature(grid, line, projected, lineHeight, baseline, f
|
|
|
5709
5709
|
// src/components/TextEntity.ts
|
|
5710
5710
|
import {
|
|
5711
5711
|
LayoutEngine,
|
|
5712
|
-
|
|
5712
|
+
resolveGlyphMeasurer
|
|
5713
5713
|
} from "@vectojs/layout";
|
|
5714
|
-
var sharedMeasurer;
|
|
5714
|
+
var sharedMeasurer = null;
|
|
5715
5715
|
function defaultMeasurer() {
|
|
5716
|
-
|
|
5716
|
+
sharedMeasurer ??= resolveGlyphMeasurer("sans-serif");
|
|
5717
5717
|
return sharedMeasurer;
|
|
5718
5718
|
}
|
|
5719
5719
|
var TextEntity = class extends Entity {
|
package/dist/renderer.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var _chunkKHGHP2J3js = require('./chunk-KHGHP2J3.js');
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
@@ -16,4 +16,4 @@ var _chunkKEBYJVD6js = require('./chunk-KEBYJVD6.js');
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
exports.CanvasRenderer =
|
|
19
|
+
exports.CanvasRenderer = _chunkKHGHP2J3js.CanvasRenderer; exports.GlyphRasterAtlas = _chunkKHGHP2J3js.GlyphRasterAtlas; exports.SVGRenderer = _chunkKHGHP2J3js.SVGRenderer; exports.TextRasterCache = _chunkKHGHP2J3js.TextRasterCache; exports.WebGPUParticleSystemManager = _chunkKHGHP2J3js.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkKHGHP2J3js.createWebGLPointRenderer; exports.parseColorToRGBA = _chunkKHGHP2J3js.parseColorToRGBA;
|
package/dist/renderer.mjs
CHANGED
|
@@ -34,10 +34,34 @@ export declare class MSDFTextEntity extends Entity {
|
|
|
34
34
|
private layoutText;
|
|
35
35
|
private text;
|
|
36
36
|
private lastRenderedSeqId;
|
|
37
|
+
private atlasDecodeTarget;
|
|
38
|
+
private atlasDecodeHandler;
|
|
37
39
|
private rgbColorCache;
|
|
38
40
|
private fontStringCache;
|
|
39
41
|
private layoutResult;
|
|
40
42
|
constructor(text: string, options: MSDFTextEntityOptions);
|
|
43
|
+
/**
|
|
44
|
+
* Repaint once the atlas raster decodes.
|
|
45
|
+
*
|
|
46
|
+
* The WebGL backend refuses to upload a not-yet-decoded atlas (it would pin an
|
|
47
|
+
* empty texture in its identity cache forever), so the upload has to happen on
|
|
48
|
+
* a LATER frame — and nothing else schedules one. Layout marks the scene dirty
|
|
49
|
+
* when the worker replies, which for a network-served atlas is long before the
|
|
50
|
+
* image lands, so the scene is already idle by then.
|
|
51
|
+
*
|
|
52
|
+
* Measured on Chromium and Firefox (2026-07-31) with a 600 ms atlas: the
|
|
53
|
+
* scene's own rAF loop never uploaded a decoded atlas in EITHER render mode.
|
|
54
|
+
* `onDemand` skips idle frames outright; `always` throttles to 2 FPS when
|
|
55
|
+
* idle, so whether it recovers is down to whether a throttled tick happens to
|
|
56
|
+
* land after the decode — Chromium got one, Firefox did not. Neither is a
|
|
57
|
+
* mechanism, which is why this listener exists rather than relying on the
|
|
58
|
+
* frame loop to come back around.
|
|
59
|
+
*
|
|
60
|
+
* Only `HTMLImageElement`-shaped sources have a decode to wait for; a canvas,
|
|
61
|
+
* `ImageBitmap`, or `VideoFrame` atlas is ready on arrival.
|
|
62
|
+
*/
|
|
63
|
+
private watchAtlasDecode;
|
|
64
|
+
private detachAtlasDecodeListener;
|
|
41
65
|
/** Change the wrap boundary and re-run layout for the current text. */
|
|
42
66
|
setMaxWidth(maxWidth: number): void;
|
|
43
67
|
/**
|
package/dist/text/SVGEntity.d.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { Entity } from '../tree/Entity';
|
|
2
2
|
import { IRenderer } from '../renderer/IRenderer';
|
|
3
3
|
export declare class SVGEntity extends Entity {
|
|
4
|
+
/**
|
|
5
|
+
* Stroke colour of the fallback marker drawn when the source cannot be
|
|
6
|
+
* rasterized. Set to `'transparent'` to opt out and keep the box empty.
|
|
7
|
+
* Default `'rgba(248,113,113,0.9)'`.
|
|
8
|
+
*/
|
|
9
|
+
fallbackStroke: string;
|
|
10
|
+
/** Fill behind the fallback marker. Default `'rgba(248,113,113,0.12)'`. */
|
|
11
|
+
fallbackFill: string;
|
|
4
12
|
private svgSource;
|
|
5
13
|
private imageBitmap;
|
|
6
14
|
private imageElement;
|
|
7
15
|
private blobURL;
|
|
8
16
|
private currentImg;
|
|
9
17
|
private lodTimeout;
|
|
18
|
+
private rasterFailed;
|
|
10
19
|
private cachedDoc;
|
|
11
20
|
private baseWidth;
|
|
12
21
|
private baseHeight;
|
|
@@ -16,7 +25,22 @@ export declare class SVGEntity extends Entity {
|
|
|
16
25
|
setSVGSource(svgSource: string): void;
|
|
17
26
|
private parseSVGDimensions;
|
|
18
27
|
private triggerRasterization;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the source genuinely rasterized to a bitmap.
|
|
30
|
+
*
|
|
31
|
+
* Distinguishes "drew the real artwork" from "drew the fallback marker",
|
|
32
|
+
* which pixel counts alone cannot tell apart — both are non-blank.
|
|
33
|
+
*/
|
|
34
|
+
hasRasterBitmap(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Whether rasterization failed, so {@link render} draws the fallback marker.
|
|
37
|
+
*
|
|
38
|
+
* `false` while a raster is still in flight; only a settled failure sets it.
|
|
39
|
+
*/
|
|
40
|
+
hasRasterFailed(): boolean;
|
|
19
41
|
isPointInside(globalX: number, globalY: number): boolean;
|
|
20
42
|
render(r: IRenderer): void;
|
|
43
|
+
/** Box outline plus a diagonal cross — the conventional "broken image" mark. */
|
|
44
|
+
private drawFallback;
|
|
21
45
|
destroy(): void;
|
|
22
46
|
}
|
package/dist/text.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkZ3HFY75Rjs = require('./chunk-Z3HFY75R.js');
|
|
6
6
|
|
|
7
7
|
// src/text/index.ts
|
|
8
8
|
var _text = require('@vectojs/text'); _createStarExport(_text);
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
exports.MSDFTextEntity =
|
|
12
|
+
exports.MSDFTextEntity = _chunkZ3HFY75Rjs.MSDFTextEntity; exports.SVGEntity = _chunkZ3HFY75Rjs.SVGEntity;
|
package/dist/text.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vectojs/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -61,20 +61,20 @@
|
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "(cd ../math && bun run build) && (cd ../text && bun run build) && (cd ../layout && bun run build) && (cd ../animation && bun run build) && tsup && tsc -p tsconfig.build.json",
|
|
63
63
|
"test": "vitest run",
|
|
64
|
-
"test:e2e": "bun e2e/hidpi.e2e.ts && bun e2e/text-projection.e2e.ts"
|
|
64
|
+
"test:e2e": "bun e2e/hidpi.e2e.ts && bun e2e/text-projection.e2e.ts && bun e2e/svg-fallback.e2e.ts && bun e2e/msdf-atlas-decode.e2e.ts && bun e2e/layout-worker-fallback.e2e.ts"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@vectojs/animation": "^0.1.1",
|
|
68
|
-
"@vectojs/layout": "^0.
|
|
68
|
+
"@vectojs/layout": "^0.5.0",
|
|
69
69
|
"@vectojs/math": "^0.1.1",
|
|
70
|
-
"@vectojs/text": "^0.
|
|
70
|
+
"@vectojs/text": "^0.3.0"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@guidepup/virtual-screen-reader": "0.32.1",
|
|
74
74
|
"@vitest/coverage-v8": "^4.1.10",
|
|
75
75
|
"esbuild": "^0.28.1",
|
|
76
|
-
"jsdom": "^
|
|
77
|
-
"puppeteer-core": "^25.
|
|
76
|
+
"jsdom": "^30.0.1",
|
|
77
|
+
"puppeteer-core": "^25.4.0",
|
|
78
78
|
"tsup": "^8.3.5",
|
|
79
79
|
"vitest": "^4.1.10"
|
|
80
80
|
}
|