@vectojs/core 0.2.2 → 0.2.4
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/README.md +110 -34
- package/dist/{chunk-RW6NC4RB.js → chunk-76NMTLYL.js} +56 -14
- package/dist/{chunk-YA2J5ZH7.mjs → chunk-B3Z3JEJH.mjs} +50 -8
- package/dist/{chunk-72WVPMSJ.js → chunk-IKLYTHAL.js} +8 -8
- package/dist/{chunk-T456DL4P.mjs → chunk-MCULB5VC.mjs} +1 -1
- package/dist/{chunk-H3QIE77O.mjs → chunk-P6MDWIEX.mjs} +216 -61
- package/dist/{chunk-LIX7DJTI.js → chunk-TPNADFNN.js} +143 -15
- package/dist/{chunk-LA3FJLP2.js → chunk-TQ4H357Q.js} +226 -71
- package/dist/{chunk-2Y45S4JK.mjs → chunk-WEQRBXT2.mjs} +142 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +185 -119
- package/dist/index.mjs +147 -81
- package/dist/layout/LayoutWorkerManager.d.ts +4 -0
- package/dist/layout/LayoutWorkerSource.d.ts +1 -1
- package/dist/layout.js +3 -3
- package/dist/layout.mjs +2 -2
- package/dist/renderer/CanvasRenderer.d.ts +6 -0
- package/dist/renderer/IRenderer.d.ts +9 -0
- package/dist/renderer/SVGRenderer.d.ts +14 -0
- package/dist/renderer/url.d.ts +31 -0
- package/dist/renderer.js +2 -2
- package/dist/renderer.mjs +1 -1
- package/dist/text.js +3 -3
- package/dist/text.mjs +2 -2
- package/dist/tree/DOMPortalEntity.d.ts +2 -1
- package/dist/tree/Entity.d.ts +48 -7
- package/dist/tree/Scene.d.ts +9 -1
- package/package.json +2 -4
|
@@ -185,9 +185,100 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
185
185
|
}
|
|
186
186
|
return grad;
|
|
187
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Canvas2D drawing contexts are automatically released when their
|
|
190
|
+
* `<canvas>` element is GC'd, so there's no explicit GPU handle to free.
|
|
191
|
+
* This method clears our internal batch state and is idempotent.
|
|
192
|
+
*/
|
|
193
|
+
dispose() {
|
|
194
|
+
this.batchCount = 0;
|
|
195
|
+
this.batchColor = "";
|
|
196
|
+
this.batchAlpha = 1;
|
|
197
|
+
this.batchActive = false;
|
|
198
|
+
}
|
|
188
199
|
};
|
|
189
200
|
|
|
201
|
+
// src/renderer/url.ts
|
|
202
|
+
var SAFE_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:", "ftp:"]);
|
|
203
|
+
var SCHEME_PATTERN = /^[a-z][a-z\d+.-]*$/i;
|
|
204
|
+
function normalizeSchemeCandidate(value) {
|
|
205
|
+
let result = "";
|
|
206
|
+
for (const character of value) {
|
|
207
|
+
const code = character.charCodeAt(0);
|
|
208
|
+
if (code > 32 && (code < 127 || code > 159)) result += character;
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
function hasSafeSchemeOrIsRelative(url) {
|
|
213
|
+
const colonIndex = url.indexOf(":");
|
|
214
|
+
if (colonIndex < 0) return true;
|
|
215
|
+
const candidate = normalizeSchemeCandidate(url.slice(0, colonIndex));
|
|
216
|
+
if (!SCHEME_PATTERN.test(candidate)) return true;
|
|
217
|
+
return SAFE_SCHEMES.has(`${candidate.toLowerCase()}:`);
|
|
218
|
+
}
|
|
219
|
+
function sanitizeUrl(href) {
|
|
220
|
+
if (typeof href !== "string") return "";
|
|
221
|
+
const trimmed = href.trim();
|
|
222
|
+
if (trimmed === "") return "";
|
|
223
|
+
return hasSafeSchemeOrIsRelative(trimmed) ? trimmed : "#";
|
|
224
|
+
}
|
|
225
|
+
function isSafeUrl(urlStr) {
|
|
226
|
+
if (typeof urlStr !== "string") return false;
|
|
227
|
+
const trimmed = urlStr.trim();
|
|
228
|
+
if (trimmed === "") return false;
|
|
229
|
+
return hasSafeSchemeOrIsRelative(trimmed);
|
|
230
|
+
}
|
|
231
|
+
|
|
190
232
|
// src/renderer/SVGRenderer.ts
|
|
233
|
+
function parseFontSizeToken(font) {
|
|
234
|
+
for (let i = 0; i < font.length; i++) {
|
|
235
|
+
const ch = font[i];
|
|
236
|
+
if (!(ch >= "0" && ch <= "9" || ch === ".")) continue;
|
|
237
|
+
let j = i + 1;
|
|
238
|
+
while (j < font.length) {
|
|
239
|
+
const next = font[j];
|
|
240
|
+
if (next >= "0" && next <= "9" || next === ".") {
|
|
241
|
+
j++;
|
|
242
|
+
} else {
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const unit = font.startsWith("rem", j) ? "rem" : font.startsWith("em", j) ? "em" : font.startsWith("px", j) ? "px" : null;
|
|
247
|
+
if (!unit) {
|
|
248
|
+
i = j;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
let end = j + unit.length;
|
|
252
|
+
if (font[end] === "/") {
|
|
253
|
+
end++;
|
|
254
|
+
while (end < font.length && font[end] !== " " && font[end] !== " ") end++;
|
|
255
|
+
}
|
|
256
|
+
const value = Number.parseFloat(font.slice(i, j));
|
|
257
|
+
return Number.isFinite(value) ? { value, unit, start: i, end } : null;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
function fontFamilyFromShorthand(font, sizeToken) {
|
|
262
|
+
const withoutSize = sizeToken ? `${font.slice(0, sizeToken.start)} ${font.slice(sizeToken.end)}`.trim() : font.trim();
|
|
263
|
+
const family = withoutSize.split(" ").map((part) => part.trim()).filter(
|
|
264
|
+
(part) => part && ![
|
|
265
|
+
"bold",
|
|
266
|
+
"italic",
|
|
267
|
+
"oblique",
|
|
268
|
+
"normal",
|
|
269
|
+
"900",
|
|
270
|
+
"800",
|
|
271
|
+
"700",
|
|
272
|
+
"600",
|
|
273
|
+
"500",
|
|
274
|
+
"400",
|
|
275
|
+
"300",
|
|
276
|
+
"200",
|
|
277
|
+
"100"
|
|
278
|
+
].includes(part.toLowerCase())
|
|
279
|
+
).join(" ");
|
|
280
|
+
return family || "sans-serif";
|
|
281
|
+
}
|
|
191
282
|
var SVGRenderer = class {
|
|
192
283
|
width;
|
|
193
284
|
height;
|
|
@@ -395,7 +486,7 @@ var SVGRenderer = class {
|
|
|
395
486
|
}
|
|
396
487
|
fill(colorOrGradient) {
|
|
397
488
|
this.flush();
|
|
398
|
-
const fillVal = this.resolveGradient(colorOrGradient);
|
|
489
|
+
const fillVal = this.escapeXML(this.resolveGradient(colorOrGradient));
|
|
399
490
|
const dStr = this.currentPath.join(" ");
|
|
400
491
|
const transformStr = `matrix(${this.ma},${this.mb},${this.mc},${this.md},${this.me},${this.mf})`;
|
|
401
492
|
this.buffer.push(
|
|
@@ -404,7 +495,7 @@ var SVGRenderer = class {
|
|
|
404
495
|
}
|
|
405
496
|
stroke(colorOrGradient, lineWidth = 1) {
|
|
406
497
|
this.flush();
|
|
407
|
-
const strokeVal = this.resolveGradient(colorOrGradient);
|
|
498
|
+
const strokeVal = this.escapeXML(this.resolveGradient(colorOrGradient));
|
|
408
499
|
const dStr = this.currentPath.join(" ");
|
|
409
500
|
const transformStr = `matrix(${this.ma},${this.mb},${this.mc},${this.md},${this.me},${this.mf})`;
|
|
410
501
|
this.buffer.push(
|
|
@@ -413,18 +504,18 @@ var SVGRenderer = class {
|
|
|
413
504
|
}
|
|
414
505
|
fillText(text, x, y, font, color) {
|
|
415
506
|
this.flush();
|
|
416
|
-
const
|
|
417
|
-
let fontSize =
|
|
418
|
-
if (
|
|
507
|
+
const sizeToken = parseFontSizeToken(font);
|
|
508
|
+
let fontSize = sizeToken ? sizeToken.value : 16;
|
|
509
|
+
if (sizeToken && sizeToken.unit !== "px") {
|
|
419
510
|
fontSize = fontSize * 16;
|
|
420
511
|
}
|
|
421
|
-
const
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const fontFamily =
|
|
427
|
-
const fillVal = this.resolveGradient(color);
|
|
512
|
+
const lowerFont = font.toLowerCase();
|
|
513
|
+
const fontStyle = lowerFont.includes("italic") ? "italic" : lowerFont.includes("oblique") ? "oblique" : "normal";
|
|
514
|
+
const fontWeight = lowerFont.includes("bold") ? "bold" : ["900", "800", "700", "600", "500", "400", "300", "200", "100"].find(
|
|
515
|
+
(weight) => lowerFont.includes(weight)
|
|
516
|
+
) ?? "normal";
|
|
517
|
+
const fontFamily = fontFamilyFromShorthand(font, sizeToken);
|
|
518
|
+
const fillVal = this.escapeXML(this.resolveGradient(color));
|
|
428
519
|
const transformStr = `matrix(${this.ma},${this.mb},${this.mc},${this.md},${this.me},${this.mf})`;
|
|
429
520
|
this.buffer.push(
|
|
430
521
|
`<g transform="${transformStr}"><text x="${x}" y="${y}" font-size="${fontSize}" font-weight="${fontWeight}" font-style="${fontStyle}" font-family="${this.escapeXML(fontFamily)}" fill="${fillVal}" opacity="${this.globalAlpha}">${this.escapeXML(text)}</text></g>`
|
|
@@ -446,7 +537,9 @@ var SVGRenderer = class {
|
|
|
446
537
|
}
|
|
447
538
|
drawImage(source, dx, dy, dw, dh) {
|
|
448
539
|
this.flush();
|
|
449
|
-
const
|
|
540
|
+
const fromCanvas2 = typeof source?.toDataURL === "function";
|
|
541
|
+
const rawHref = fromCanvas2 ? source.toDataURL() : source?.src || "";
|
|
542
|
+
const href = fromCanvas2 && this.isSafeRasterDataUrl(rawHref) ? rawHref : sanitizeUrl(String(rawHref));
|
|
450
543
|
const transformStr = `matrix(${this.ma},${this.mb},${this.mc},${this.md},${this.me},${this.mf})`;
|
|
451
544
|
if (href) {
|
|
452
545
|
this.buffer.push(
|
|
@@ -459,6 +552,21 @@ var SVGRenderer = class {
|
|
|
459
552
|
console.warn("drawImage source fallback triggered");
|
|
460
553
|
}
|
|
461
554
|
}
|
|
555
|
+
/**
|
|
556
|
+
* Embed SVG markup as an isolated nested image.
|
|
557
|
+
*
|
|
558
|
+
* This explicit path is used by `SVGEntity` during vector export. General
|
|
559
|
+
* `drawImage()` input remains subject to the URL policy and therefore still
|
|
560
|
+
* rejects caller-provided SVG data URLs.
|
|
561
|
+
*/
|
|
562
|
+
drawSVG(source, dx, dy, dw, dh) {
|
|
563
|
+
this.flush();
|
|
564
|
+
const href = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`;
|
|
565
|
+
const transformStr = `matrix(${this.ma},${this.mb},${this.mc},${this.md},${this.me},${this.mf})`;
|
|
566
|
+
this.buffer.push(
|
|
567
|
+
`<image href="${this.escapeXML(href)}" x="${dx}" y="${dy}" width="${dw}" height="${dh}" transform="${transformStr}" />`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
462
570
|
flush() {
|
|
463
571
|
if (!this.batchActive || this.batchCircles.length === 0) return;
|
|
464
572
|
let d = "";
|
|
@@ -566,6 +674,17 @@ var SVGRenderer = class {
|
|
|
566
674
|
}
|
|
567
675
|
});
|
|
568
676
|
}
|
|
677
|
+
isSafeRasterDataUrl(value) {
|
|
678
|
+
return typeof value === "string" && /^data:image\/(?:png|jpeg|gif|webp);base64,[a-z\d+/=\s]*$/i.test(value);
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* SVGRenderer accumulates strings in memory; nothing external is allocated.
|
|
682
|
+
* Drop the buffers for GC and become idempotent.
|
|
683
|
+
*/
|
|
684
|
+
dispose() {
|
|
685
|
+
this.clear();
|
|
686
|
+
this.gradientCache.clear();
|
|
687
|
+
}
|
|
569
688
|
};
|
|
570
689
|
|
|
571
690
|
// src/renderer/colorParse.ts
|
|
@@ -831,6 +950,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
831
950
|
let logicalW = 0;
|
|
832
951
|
let logicalH = 0;
|
|
833
952
|
let dpr = 1;
|
|
953
|
+
let destroyed = false;
|
|
834
954
|
return {
|
|
835
955
|
resize(width, height) {
|
|
836
956
|
logicalW = width;
|
|
@@ -1037,6 +1157,8 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1037
1157
|
gl.bindVertexArray(null);
|
|
1038
1158
|
},
|
|
1039
1159
|
destroy() {
|
|
1160
|
+
if (destroyed) return;
|
|
1161
|
+
destroyed = true;
|
|
1040
1162
|
gl.deleteBuffer(pointBuffer);
|
|
1041
1163
|
gl.deleteBuffer(rectBuffer);
|
|
1042
1164
|
gl.deleteBuffer(spriteBuffer);
|
|
@@ -1326,10 +1448,14 @@ var WebGPUParticleSystemManager = class {
|
|
|
1326
1448
|
if (!this.computePipeline || !entity.computeBindGroup) return;
|
|
1327
1449
|
const uniformArray = new Float32Array(20);
|
|
1328
1450
|
const color = parseColorToRGBA(entity.baseColor);
|
|
1451
|
+
let opacity = 1;
|
|
1452
|
+
for (let current = entity; current; current = current.parent) {
|
|
1453
|
+
opacity *= current.opacity;
|
|
1454
|
+
}
|
|
1329
1455
|
uniformArray[0] = color[0];
|
|
1330
1456
|
uniformArray[1] = color[1];
|
|
1331
1457
|
uniformArray[2] = color[2];
|
|
1332
|
-
uniformArray[3] = color[3];
|
|
1458
|
+
uniformArray[3] = color[3] * opacity;
|
|
1333
1459
|
const mActive = !isNaN(mouseX) && !isNaN(mouseY) && mouseX > -9e3 && mouseY > -9e3;
|
|
1334
1460
|
uniformArray[4] = mActive ? mouseX : -9999;
|
|
1335
1461
|
uniformArray[5] = mActive ? mouseY : -9999;
|
|
@@ -1373,6 +1499,8 @@ var WebGPUParticleSystemManager = class {
|
|
|
1373
1499
|
|
|
1374
1500
|
export {
|
|
1375
1501
|
CanvasRenderer,
|
|
1502
|
+
sanitizeUrl,
|
|
1503
|
+
isSafeUrl,
|
|
1376
1504
|
SVGRenderer,
|
|
1377
1505
|
parseColorToRGBA,
|
|
1378
1506
|
createWebGLPointRenderer,
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './renderer/SVGRenderer';
|
|
|
4
4
|
export * from './renderer/WebGLPointRenderer';
|
|
5
5
|
export * from './renderer/WebGPUParticleSystemManager';
|
|
6
6
|
export * from './renderer/colorParse';
|
|
7
|
+
export * from './renderer/url';
|
|
7
8
|
export * from './tree/Entity';
|
|
8
9
|
export * from './tree/Scene';
|
|
9
10
|
export * from './components/TextEntity';
|