@pixldocs/canvas-renderer 0.5.222 → 0.5.225
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/{index-BKJxI43i.cjs → index-6nrov1rx.cjs} +1488 -525
- package/dist/index-6nrov1rx.cjs.map +1 -0
- package/dist/{index-DxL--cfL.js → index-BpViFQMO.js} +1488 -525
- package/dist/index-BpViFQMO.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/{vectorPdfExport-CDMnc_eW.cjs → vectorPdfExport-CVeK--lR.cjs} +325 -30
- package/dist/vectorPdfExport-CVeK--lR.cjs.map +1 -0
- package/dist/{vectorPdfExport-CTPOHXtQ.js → vectorPdfExport-D46sZGKA.js} +325 -30
- package/dist/vectorPdfExport-D46sZGKA.js.map +1 -0
- package/package.json +1 -1
- package/dist/index-BKJxI43i.cjs.map +0 -1
- package/dist/index-DxL--cfL.js.map +0 -1
- package/dist/vectorPdfExport-CDMnc_eW.cjs.map +0 -1
- package/dist/vectorPdfExport-CTPOHXtQ.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsPDF, ShadingPattern } from "jspdf";
|
|
2
2
|
import { svg2pdf } from "svg2pdf.js";
|
|
3
3
|
import * as fabric from "fabric";
|
|
4
|
-
import { p as parseTextMarkdown, r as renderSmartElementToSvg, g as getCanvasForPage, c as captureFabricCanvasSvgForPdf, f as findNodeById, a as getAbsoluteBounds, b as getProxiedImageUrl, d as getImageProxyFetchOptions, A as API_URL, n as normalizeShapeType, i as isElement, e as isGroup, h as buildRoundedTrianglePath, j as hasEdgeFade, k as bakeEdgeFade, l as getRoundedRectRadii, T as TRIANGLE_STROKE_MITER_LIMIT, m as getTrianglePoints } from "./index-
|
|
4
|
+
import { p as parseTextMarkdown, r as renderSmartElementToSvg, g as getCanvasForPage, c as captureFabricCanvasSvgForPdf, f as findNodeById, a as getAbsoluteBounds, b as getProxiedImageUrl, d as getImageProxyFetchOptions, A as API_URL, n as normalizeShapeType, i as isElement, e as isGroup, h as buildRoundedTrianglePath, j as hasEdgeFade, k as bakeEdgeFade, l as getRoundedRectRadii, T as TRIANGLE_STROKE_MITER_LIMIT, m as getTrianglePoints } from "./index-BpViFQMO.js";
|
|
5
5
|
import { resetPdfFontRegistry, FONT_FALLBACK_SYMBOLS, FONT_FALLBACK_MATH, FONT_FALLBACK_DEVANAGARI, embedFontWithGoogleFallback, getEmbeddedVariantsList, isFontAvailable, isFamilyEmbedded, resolveBestRegisteredVariant, getEmbeddedJsPDFFontName, resolveFontWeight, doesVariantSupportChar } from "./pdfFonts-DhEaMTZl.js";
|
|
6
6
|
async function embedFontsForSvg(pdf, svgStr) {
|
|
7
7
|
var _a;
|
|
@@ -984,6 +984,100 @@ function parseInlineSvgStyleDeclarations(styleText) {
|
|
|
984
984
|
};
|
|
985
985
|
}).filter((x) => !!x);
|
|
986
986
|
}
|
|
987
|
+
function shortPdfDebug(value, max = 180) {
|
|
988
|
+
if (!value) return null;
|
|
989
|
+
const str = String(value).replace(/\s+/g, " ").trim();
|
|
990
|
+
return str.length > max ? `${str.slice(0, max)}…` : str;
|
|
991
|
+
}
|
|
992
|
+
function logSvgGradientClipDiagnostics(stage, svg, context) {
|
|
993
|
+
try {
|
|
994
|
+
const gradients = Array.from(svg.querySelectorAll("linearGradient, radialGradient"));
|
|
995
|
+
const images = Array.from(svg.querySelectorAll("image"));
|
|
996
|
+
const nestedSvgs = Array.from(svg.querySelectorAll("svg")).filter((node) => node !== svg);
|
|
997
|
+
const gradientRefs = [];
|
|
998
|
+
Array.from(svg.querySelectorAll("*")).forEach((el) => {
|
|
999
|
+
const fill = el.getAttribute("fill") || getInlineStyleValue(el, "fill");
|
|
1000
|
+
const stroke = el.getAttribute("stroke") || getInlineStyleValue(el, "stroke");
|
|
1001
|
+
const fillId = extractGradientIdFromPaint(fill);
|
|
1002
|
+
const strokeId = extractGradientIdFromPaint(stroke);
|
|
1003
|
+
if (!fillId && !strokeId) return;
|
|
1004
|
+
gradientRefs.push({
|
|
1005
|
+
tag: el.tagName,
|
|
1006
|
+
id: el.getAttribute("id"),
|
|
1007
|
+
className: el.getAttribute("class"),
|
|
1008
|
+
fill: shortPdfDebug(fill, 80),
|
|
1009
|
+
stroke: shortPdfDebug(stroke, 80),
|
|
1010
|
+
fillRule: el.getAttribute("fill-rule") || getInlineStyleValue(el, "fill-rule"),
|
|
1011
|
+
transform: shortPdfDebug(el.getAttribute("transform"), 120),
|
|
1012
|
+
dStart: shortPdfDebug(el.getAttribute("d"), 160)
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
1015
|
+
console.log("[client-pdf-export][gradient-svg-diag]", {
|
|
1016
|
+
stage,
|
|
1017
|
+
...context,
|
|
1018
|
+
root: {
|
|
1019
|
+
width: svg.getAttribute("width"),
|
|
1020
|
+
height: svg.getAttribute("height"),
|
|
1021
|
+
viewBox: svg.getAttribute("viewBox")
|
|
1022
|
+
},
|
|
1023
|
+
counts: {
|
|
1024
|
+
gradients: gradients.length,
|
|
1025
|
+
gradientRefs: gradientRefs.length,
|
|
1026
|
+
images: images.length,
|
|
1027
|
+
nestedSvgs: nestedSvgs.length,
|
|
1028
|
+
paths: svg.querySelectorAll("path").length,
|
|
1029
|
+
clips: svg.querySelectorAll("clipPath").length,
|
|
1030
|
+
masks: svg.querySelectorAll("mask").length
|
|
1031
|
+
},
|
|
1032
|
+
gradients: gradients.slice(0, 8).map((g) => ({
|
|
1033
|
+
tag: g.tagName,
|
|
1034
|
+
id: g.getAttribute("id"),
|
|
1035
|
+
units: g.getAttribute("gradientUnits"),
|
|
1036
|
+
transform: g.getAttribute("gradientTransform"),
|
|
1037
|
+
x1: g.getAttribute("x1"),
|
|
1038
|
+
y1: g.getAttribute("y1"),
|
|
1039
|
+
x2: g.getAttribute("x2"),
|
|
1040
|
+
y2: g.getAttribute("y2"),
|
|
1041
|
+
stops: Array.from(g.querySelectorAll("stop")).map((s) => ({ offset: s.getAttribute("offset"), color: s.getAttribute("stop-color"), opacity: s.getAttribute("stop-opacity") })).slice(0, 5)
|
|
1042
|
+
})),
|
|
1043
|
+
images: images.slice(0, 8).map((img) => ({
|
|
1044
|
+
id: img.getAttribute("id"),
|
|
1045
|
+
href: shortPdfDebug(getSvgImageHref(img), 220),
|
|
1046
|
+
x: img.getAttribute("x"),
|
|
1047
|
+
y: img.getAttribute("y"),
|
|
1048
|
+
width: img.getAttribute("width"),
|
|
1049
|
+
height: img.getAttribute("height"),
|
|
1050
|
+
transform: shortPdfDebug(img.getAttribute("transform"), 140),
|
|
1051
|
+
clipPath: img.getAttribute("clip-path"),
|
|
1052
|
+
preserveAspectRatio: img.getAttribute("preserveAspectRatio")
|
|
1053
|
+
})),
|
|
1054
|
+
clipPaths: Array.from(svg.querySelectorAll("clipPath")).slice(0, 8).map((clip) => ({
|
|
1055
|
+
id: clip.getAttribute("id"),
|
|
1056
|
+
units: clip.getAttribute("clipPathUnits"),
|
|
1057
|
+
transform: shortPdfDebug(clip.getAttribute("transform"), 140),
|
|
1058
|
+
rects: Array.from(clip.querySelectorAll("rect")).slice(0, 3).map((rect) => ({
|
|
1059
|
+
x: rect.getAttribute("x"),
|
|
1060
|
+
y: rect.getAttribute("y"),
|
|
1061
|
+
width: rect.getAttribute("width"),
|
|
1062
|
+
height: rect.getAttribute("height"),
|
|
1063
|
+
transform: shortPdfDebug(rect.getAttribute("transform"), 140)
|
|
1064
|
+
}))
|
|
1065
|
+
})),
|
|
1066
|
+
nestedSvgs: nestedSvgs.slice(0, 8).map((node) => ({
|
|
1067
|
+
id: node.getAttribute("id"),
|
|
1068
|
+
x: node.getAttribute("x"),
|
|
1069
|
+
y: node.getAttribute("y"),
|
|
1070
|
+
width: node.getAttribute("width"),
|
|
1071
|
+
height: node.getAttribute("height"),
|
|
1072
|
+
viewBox: node.getAttribute("viewBox"),
|
|
1073
|
+
transform: shortPdfDebug(node.getAttribute("transform"), 140)
|
|
1074
|
+
})),
|
|
1075
|
+
gradientRefs: gradientRefs.slice(0, 10)
|
|
1076
|
+
});
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
console.warn("[client-pdf-export][gradient-svg-diag] failed", { stage, err });
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
987
1081
|
function logGradientBindingDiagnostics(svg) {
|
|
988
1082
|
const gradientCount = svg.querySelectorAll("linearGradient, radialGradient").length;
|
|
989
1083
|
if (gradientCount === 0) return;
|
|
@@ -1347,7 +1441,39 @@ function decodeSvgDataUri(href) {
|
|
|
1347
1441
|
}
|
|
1348
1442
|
}
|
|
1349
1443
|
}
|
|
1350
|
-
function
|
|
1444
|
+
async function readNestedSvgImageMarkup(href) {
|
|
1445
|
+
if (!href) return null;
|
|
1446
|
+
if (href.startsWith("data:image/svg+xml")) return decodeSvgDataUri(href);
|
|
1447
|
+
if (/\.svg(?:[?#]|$)/i.test(href) || href.startsWith("blob:")) {
|
|
1448
|
+
const urls = [];
|
|
1449
|
+
const addUrl = (url) => {
|
|
1450
|
+
if (url && !urls.includes(url)) urls.push(url);
|
|
1451
|
+
};
|
|
1452
|
+
if (href.startsWith("blob:")) {
|
|
1453
|
+
addUrl(href);
|
|
1454
|
+
} else {
|
|
1455
|
+
addUrl(getProxiedImageUrl(href));
|
|
1456
|
+
addUrl(href);
|
|
1457
|
+
try {
|
|
1458
|
+
const proxyUrl = new URL(`${API_URL}/image-proxy`);
|
|
1459
|
+
proxyUrl.searchParams.set("url", href);
|
|
1460
|
+
addUrl(proxyUrl.toString());
|
|
1461
|
+
} catch {
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
for (const url of urls) {
|
|
1465
|
+
try {
|
|
1466
|
+
const res = await fetch(url, { cache: "no-store", ...getImageProxyFetchOptions() });
|
|
1467
|
+
if (!res.ok) continue;
|
|
1468
|
+
const text = await res.text();
|
|
1469
|
+
if (/<svg[\s>]/i.test(text)) return text;
|
|
1470
|
+
} catch {
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return null;
|
|
1475
|
+
}
|
|
1476
|
+
async function inlineNestedSvgImageDataUris(svgString, domParser = new DOMParser()) {
|
|
1351
1477
|
var _a;
|
|
1352
1478
|
try {
|
|
1353
1479
|
const doc = domParser.parseFromString(svgString, "image/svg+xml");
|
|
@@ -1355,42 +1481,102 @@ function inlineNestedSvgImageDataUris(svgString, domParser = new DOMParser()) {
|
|
|
1355
1481
|
const root = doc.documentElement;
|
|
1356
1482
|
if (!root || root.tagName.toLowerCase() !== "svg") return svgString;
|
|
1357
1483
|
let changed = false;
|
|
1484
|
+
let inlined = 0;
|
|
1485
|
+
let failed = 0;
|
|
1358
1486
|
const images = Array.from(doc.querySelectorAll("image"));
|
|
1359
|
-
|
|
1487
|
+
logSvgGradientClipDiagnostics("inline-pass:start", root, { imageCount: images.length });
|
|
1488
|
+
for (const [imageIndex, img] of images.entries()) {
|
|
1360
1489
|
const href = img.getAttribute("href") || img.getAttributeNS("http://www.w3.org/1999/xlink", "href");
|
|
1361
|
-
if (!href
|
|
1490
|
+
if (!href) continue;
|
|
1362
1491
|
try {
|
|
1363
|
-
const svgContent =
|
|
1364
|
-
if (!svgContent || !/<svg[\s>]/i.test(svgContent))
|
|
1492
|
+
const svgContent = await readNestedSvgImageMarkup(href);
|
|
1493
|
+
if (!svgContent || !/<svg[\s>]/i.test(svgContent)) {
|
|
1494
|
+
if (/\.svg(?:[?#]|$)/i.test(href) || href.startsWith("blob:") || href.startsWith("data:image/svg+xml")) {
|
|
1495
|
+
console.warn("[client-pdf-export][gradient-svg-diag] inline source not readable", {
|
|
1496
|
+
imageIndex,
|
|
1497
|
+
href: shortPdfDebug(href, 260),
|
|
1498
|
+
x: img.getAttribute("x"),
|
|
1499
|
+
y: img.getAttribute("y"),
|
|
1500
|
+
width: img.getAttribute("width"),
|
|
1501
|
+
height: img.getAttribute("height"),
|
|
1502
|
+
transform: shortPdfDebug(img.getAttribute("transform"), 160)
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1365
1507
|
const innerDoc = domParser.parseFromString(svgContent, "image/svg+xml");
|
|
1366
1508
|
if (innerDoc.querySelector("parsererror")) continue;
|
|
1367
1509
|
const innerSvg = innerDoc.documentElement;
|
|
1368
1510
|
if (!innerSvg || innerSvg.tagName.toLowerCase() !== "svg") continue;
|
|
1511
|
+
const sourceId = img.getAttribute("id") || `image-${imageIndex}`;
|
|
1512
|
+
prefixSvgIds(innerSvg, `inline-${sourceId}`);
|
|
1369
1513
|
const ix = parseFloat(img.getAttribute("x") || "0") || 0;
|
|
1370
1514
|
const iy = parseFloat(img.getAttribute("y") || "0") || 0;
|
|
1371
1515
|
const iw = parseFloat(img.getAttribute("width") || "0");
|
|
1372
1516
|
const ih = parseFloat(img.getAttribute("height") || "0");
|
|
1373
|
-
if (!(iw > 0 && ih > 0))
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1517
|
+
if (!(iw > 0 && ih > 0)) {
|
|
1518
|
+
console.warn("[client-pdf-export][gradient-svg-diag] inline skipped: image has invalid frame", {
|
|
1519
|
+
imageIndex,
|
|
1520
|
+
href: shortPdfDebug(href, 260),
|
|
1521
|
+
x: img.getAttribute("x"),
|
|
1522
|
+
y: img.getAttribute("y"),
|
|
1523
|
+
width: img.getAttribute("width"),
|
|
1524
|
+
height: img.getAttribute("height"),
|
|
1525
|
+
innerViewBox: innerSvg.getAttribute("viewBox")
|
|
1526
|
+
});
|
|
1527
|
+
continue;
|
|
1378
1528
|
}
|
|
1379
|
-
nestedSvg.setAttribute("x", "0");
|
|
1380
|
-
nestedSvg.setAttribute("y", "0");
|
|
1381
|
-
nestedSvg.setAttribute("width", String(iw));
|
|
1382
|
-
nestedSvg.setAttribute("height", String(ih));
|
|
1383
|
-
nestedSvg.setAttribute(
|
|
1384
|
-
"preserveAspectRatio",
|
|
1385
|
-
img.getAttribute("preserveAspectRatio") || nestedSvg.getAttribute("preserveAspectRatio") || "xMidYMid meet"
|
|
1386
|
-
);
|
|
1387
1529
|
const g = doc.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1388
1530
|
const existingTransform = img.getAttribute("transform") || "";
|
|
1531
|
+
const vb = (innerSvg.getAttribute("viewBox") || "").trim().split(/[\s,]+/).map((n) => Number.parseFloat(n));
|
|
1532
|
+
const [vbX, vbY, vbW, vbH] = vb.length === 4 && vb.every((n) => Number.isFinite(n)) && vb[2] > 0 && vb[3] > 0 ? vb : [0, 0, iw, ih];
|
|
1533
|
+
const par = img.getAttribute("preserveAspectRatio") || innerSvg.getAttribute("preserveAspectRatio") || "xMidYMid meet";
|
|
1534
|
+
const parParts = par.trim().split(/\s+/).filter(Boolean);
|
|
1535
|
+
const align = parParts[0] === "defer" ? parParts[1] || "xMidYMid" : parParts[0] || "xMidYMid";
|
|
1536
|
+
const meetOrSlice = parParts[0] === "defer" ? parParts[2] || "meet" : parParts[1] || "meet";
|
|
1537
|
+
let sx = iw / vbW;
|
|
1538
|
+
let sy = ih / vbH;
|
|
1539
|
+
let ox = 0;
|
|
1540
|
+
let oy = 0;
|
|
1541
|
+
if (align !== "none") {
|
|
1542
|
+
const s = meetOrSlice === "slice" ? Math.max(sx, sy) : Math.min(sx, sy);
|
|
1543
|
+
sx = s;
|
|
1544
|
+
sy = s;
|
|
1545
|
+
const extraW = iw - vbW * s;
|
|
1546
|
+
const extraH = ih - vbH * s;
|
|
1547
|
+
if (align.includes("xMid")) ox = extraW / 2;
|
|
1548
|
+
else if (align.includes("xMax")) ox = extraW;
|
|
1549
|
+
if (align.includes("YMid")) oy = extraH / 2;
|
|
1550
|
+
else if (align.includes("YMax")) oy = extraH;
|
|
1551
|
+
}
|
|
1389
1552
|
let transform = existingTransform;
|
|
1390
|
-
transform += `${transform ? " " : ""}translate(${ix},${iy})`;
|
|
1553
|
+
transform += `${transform ? " " : ""}translate(${ix + ox},${iy + oy}) scale(${sx},${sy}) translate(${-vbX},${-vbY})`;
|
|
1391
1554
|
if (transform) {
|
|
1392
1555
|
g.setAttribute("transform", transform);
|
|
1393
1556
|
}
|
|
1557
|
+
logSvgGradientClipDiagnostics("inline-pass:image-inlined", innerSvg, {
|
|
1558
|
+
imageIndex,
|
|
1559
|
+
sourceId,
|
|
1560
|
+
href: shortPdfDebug(href, 260),
|
|
1561
|
+
imageFrame: { x: ix, y: iy, width: iw, height: ih },
|
|
1562
|
+
innerRoot: {
|
|
1563
|
+
width: innerSvg.getAttribute("width"),
|
|
1564
|
+
height: innerSvg.getAttribute("height"),
|
|
1565
|
+
viewBox: innerSvg.getAttribute("viewBox"),
|
|
1566
|
+
preserveAspectRatio: innerSvg.getAttribute("preserveAspectRatio")
|
|
1567
|
+
},
|
|
1568
|
+
appliedTransform: transform
|
|
1569
|
+
});
|
|
1570
|
+
const imageClipPath = img.getAttribute("clip-path") || getInlineStyleValue(img, "clip-path");
|
|
1571
|
+
const dropImageClipPath = isRedundantImageClipPathForInlineSvg(root, imageClipPath, ix, iy, iw, ih);
|
|
1572
|
+
if (dropImageClipPath) {
|
|
1573
|
+
console.log("[client-pdf-export][gradient-svg-diag] dropped redundant inline SVG image clipPath", {
|
|
1574
|
+
imageIndex,
|
|
1575
|
+
sourceId,
|
|
1576
|
+
clipPath: imageClipPath,
|
|
1577
|
+
imageFrame: { x: ix, y: iy, width: iw, height: ih }
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1394
1580
|
const passthroughAttrs = /* @__PURE__ */ new Set([
|
|
1395
1581
|
"id",
|
|
1396
1582
|
"class",
|
|
@@ -1404,21 +1590,123 @@ function inlineNestedSvgImageDataUris(svgString, domParser = new DOMParser()) {
|
|
|
1404
1590
|
"pointer-events"
|
|
1405
1591
|
]);
|
|
1406
1592
|
Array.from(img.attributes).forEach((attr) => {
|
|
1593
|
+
if (dropImageClipPath && attr.name === "clip-path") return;
|
|
1594
|
+
if (dropImageClipPath && attr.name === "style") {
|
|
1595
|
+
const kept = parseInlineSvgStyleDeclarations(attr.value).filter((decl) => decl.key !== "clip-path").map((decl) => `${decl.key}: ${decl.value}`).join("; ");
|
|
1596
|
+
if (kept) g.setAttribute("style", kept);
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1407
1599
|
if (passthroughAttrs.has(attr.name)) {
|
|
1408
1600
|
g.setAttribute(attr.name, attr.value);
|
|
1409
1601
|
}
|
|
1410
1602
|
});
|
|
1411
|
-
|
|
1603
|
+
for (const child of Array.from(innerSvg.childNodes)) {
|
|
1604
|
+
g.appendChild(doc.importNode(child, true));
|
|
1605
|
+
}
|
|
1412
1606
|
(_a = img.parentNode) == null ? void 0 : _a.replaceChild(g, img);
|
|
1413
1607
|
changed = true;
|
|
1608
|
+
inlined++;
|
|
1414
1609
|
} catch {
|
|
1610
|
+
failed++;
|
|
1415
1611
|
}
|
|
1416
1612
|
}
|
|
1613
|
+
if (inlined > 0 || failed > 0) {
|
|
1614
|
+
console.log("[client-pdf-export] nested SVG image inline pass", { inlined, failed, imageCount: images.length, changed });
|
|
1615
|
+
logSvgGradientClipDiagnostics("inline-pass:done", doc.documentElement, { inlined, failed, imageCount: images.length, changed });
|
|
1616
|
+
}
|
|
1417
1617
|
return changed ? new XMLSerializer().serializeToString(doc.documentElement) : svgString;
|
|
1418
1618
|
} catch {
|
|
1419
1619
|
return svgString;
|
|
1420
1620
|
}
|
|
1421
1621
|
}
|
|
1622
|
+
function parseSvgLength(value, fallback) {
|
|
1623
|
+
if (!value) return fallback;
|
|
1624
|
+
const trimmed = value.trim();
|
|
1625
|
+
if (!trimmed || trimmed.endsWith("%")) return fallback;
|
|
1626
|
+
const parsed = Number.parseFloat(trimmed);
|
|
1627
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
1628
|
+
}
|
|
1629
|
+
function isIdentitySvgMatrix(value) {
|
|
1630
|
+
var _a;
|
|
1631
|
+
if (!value) return true;
|
|
1632
|
+
const nums = ((_a = value.match(/-?\d*\.?\d+(?:e[-+]?\d+)?/gi)) == null ? void 0 : _a.map(Number)) ?? [];
|
|
1633
|
+
return nums.length === 6 && Math.abs(nums[0] - 1) < 1e-3 && Math.abs(nums[1]) < 1e-3 && Math.abs(nums[2]) < 1e-3 && Math.abs(nums[3] - 1) < 1e-3 && Math.abs(nums[4]) < 1e-3 && Math.abs(nums[5]) < 1e-3;
|
|
1634
|
+
}
|
|
1635
|
+
function isRedundantImageClipPathForInlineSvg(root, clipPathRef, ix, iy, iw, ih) {
|
|
1636
|
+
const clipId = extractGradientIdFromPaint(clipPathRef);
|
|
1637
|
+
if (!clipId || !(iw > 0 && ih > 0)) return false;
|
|
1638
|
+
let clip = null;
|
|
1639
|
+
for (const el of root.querySelectorAll("[id]")) {
|
|
1640
|
+
if (el.getAttribute("id") === clipId) {
|
|
1641
|
+
clip = el;
|
|
1642
|
+
break;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
if (!clip || clip.tagName.toLowerCase() !== "clippath") return false;
|
|
1646
|
+
const units = (clip.getAttribute("clipPathUnits") || "userSpaceOnUse").toLowerCase();
|
|
1647
|
+
const rects = Array.from(clip.children).filter((el) => el.tagName.toLowerCase() === "rect");
|
|
1648
|
+
if (rects.length !== 1 || rects[0].parentElement !== clip) return false;
|
|
1649
|
+
const rect = rects[0];
|
|
1650
|
+
if (!isIdentitySvgMatrix(rect.getAttribute("transform"))) return false;
|
|
1651
|
+
const rx = Number.parseFloat(rect.getAttribute("x") || "0") || 0;
|
|
1652
|
+
const ry = Number.parseFloat(rect.getAttribute("y") || "0") || 0;
|
|
1653
|
+
const rw = Number.parseFloat(rect.getAttribute("width") || "0");
|
|
1654
|
+
const rh = Number.parseFloat(rect.getAttribute("height") || "0");
|
|
1655
|
+
if (!(rw > 0 && rh > 0)) return false;
|
|
1656
|
+
const near = (a, b) => Math.abs(a - b) <= Math.max(1, Math.max(Math.abs(a), Math.abs(b)) * 0.01);
|
|
1657
|
+
if (units === "objectboundingbox") return near(rx, 0) && near(ry, 0) && near(rw, 1) && near(rh, 1);
|
|
1658
|
+
return near(rx, ix) && near(ry, iy) && near(rw, iw) && near(rh, ih);
|
|
1659
|
+
}
|
|
1660
|
+
function flattenNestedSvgViewports(rootSvg) {
|
|
1661
|
+
const nestedSvgs = Array.from(rootSvg.querySelectorAll("svg")).filter((svg) => svg !== rootSvg).reverse();
|
|
1662
|
+
if (nestedSvgs.length === 0) return;
|
|
1663
|
+
let flattened = 0;
|
|
1664
|
+
for (const nestedSvg of nestedSvgs) {
|
|
1665
|
+
const parent = nestedSvg.parentNode;
|
|
1666
|
+
const doc = nestedSvg.ownerDocument;
|
|
1667
|
+
if (!parent || !doc) continue;
|
|
1668
|
+
const vb = (nestedSvg.getAttribute("viewBox") || "").trim().split(/[\s,]+/).map((n) => Number.parseFloat(n));
|
|
1669
|
+
const hasViewBox = vb.length === 4 && vb.every((n) => Number.isFinite(n)) && vb[2] > 0 && vb[3] > 0;
|
|
1670
|
+
const [vbX, vbY, vbW, vbH] = hasViewBox ? vb : [0, 0, 0, 0];
|
|
1671
|
+
const width = parseSvgLength(nestedSvg.getAttribute("width"), hasViewBox ? vbW : 0);
|
|
1672
|
+
const height = parseSvgLength(nestedSvg.getAttribute("height"), hasViewBox ? vbH : 0);
|
|
1673
|
+
if (!(width > 0 && height > 0)) continue;
|
|
1674
|
+
const x = Number.parseFloat(nestedSvg.getAttribute("x") || "0") || 0;
|
|
1675
|
+
const y = Number.parseFloat(nestedSvg.getAttribute("y") || "0") || 0;
|
|
1676
|
+
const par = nestedSvg.getAttribute("preserveAspectRatio") || "xMidYMid meet";
|
|
1677
|
+
const parParts = par.trim().split(/\s+/).filter(Boolean);
|
|
1678
|
+
const align = parParts[0] === "defer" ? parParts[1] || "xMidYMid" : parParts[0] || "xMidYMid";
|
|
1679
|
+
const meetOrSlice = parParts[0] === "defer" ? parParts[2] || "meet" : parParts[1] || "meet";
|
|
1680
|
+
let sx = hasViewBox ? width / vbW : 1;
|
|
1681
|
+
let sy = hasViewBox ? height / vbH : 1;
|
|
1682
|
+
let ox = 0;
|
|
1683
|
+
let oy = 0;
|
|
1684
|
+
if (hasViewBox && align !== "none") {
|
|
1685
|
+
const s = meetOrSlice === "slice" ? Math.max(sx, sy) : Math.min(sx, sy);
|
|
1686
|
+
sx = s;
|
|
1687
|
+
sy = s;
|
|
1688
|
+
const extraW = width - vbW * s;
|
|
1689
|
+
const extraH = height - vbH * s;
|
|
1690
|
+
if (align.includes("xMid")) ox = extraW / 2;
|
|
1691
|
+
else if (align.includes("xMax")) ox = extraW;
|
|
1692
|
+
if (align.includes("YMid")) oy = extraH / 2;
|
|
1693
|
+
else if (align.includes("YMax")) oy = extraH;
|
|
1694
|
+
}
|
|
1695
|
+
const g = doc.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1696
|
+
const existingTransform = nestedSvg.getAttribute("transform") || "";
|
|
1697
|
+
const viewBoxTransform = hasViewBox ? `translate(${x + ox},${y + oy}) scale(${sx},${sy}) translate(${-vbX},${-vbY})` : `translate(${x},${y})`;
|
|
1698
|
+
g.setAttribute("transform", `${existingTransform}${existingTransform ? " " : ""}${viewBoxTransform}`);
|
|
1699
|
+
for (const attr of Array.from(nestedSvg.attributes)) {
|
|
1700
|
+
if (["id", "class", "style", "opacity", "display", "visibility", "clip-path", "mask", "filter", "pointer-events"].includes(attr.name)) {
|
|
1701
|
+
g.setAttribute(attr.name, attr.value);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
for (const child of Array.from(nestedSvg.childNodes)) g.appendChild(child);
|
|
1705
|
+
parent.replaceChild(g, nestedSvg);
|
|
1706
|
+
flattened++;
|
|
1707
|
+
}
|
|
1708
|
+
if (flattened > 0) console.log("[client-pdf-export] flattened nested SVG viewport(s)", { flattened });
|
|
1709
|
+
}
|
|
1422
1710
|
function hasInvalidSvgNumericToken(value) {
|
|
1423
1711
|
return typeof value === "string" && /\b(?:NaN|-?Infinity|undefined|null)\b/.test(value);
|
|
1424
1712
|
}
|
|
@@ -2300,11 +2588,12 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
|
|
|
2300
2588
|
var _a;
|
|
2301
2589
|
try {
|
|
2302
2590
|
const parser = new DOMParser();
|
|
2303
|
-
const processedSvg = inlineNestedSvgImageDataUris(rawSvg, parser);
|
|
2591
|
+
const processedSvg = await inlineNestedSvgImageDataUris(rawSvg, parser);
|
|
2304
2592
|
const doc = parser.parseFromString(processedSvg, "image/svg+xml");
|
|
2305
2593
|
if (doc.querySelector("parsererror")) return null;
|
|
2306
2594
|
const svg = doc.documentElement;
|
|
2307
2595
|
if (!svg || svg.tagName.toLowerCase() !== "svg") return null;
|
|
2596
|
+
logSvgGradientClipDiagnostics("prepare:parsed-after-inline", svg, { pageKey, pageWidth, pageHeight, processedLength: processedSvg.length });
|
|
2308
2597
|
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
2309
2598
|
if (/xlink:href/i.test(processedSvg) && !svg.getAttribute("xmlns:xlink")) {
|
|
2310
2599
|
svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
@@ -2314,14 +2603,18 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
|
|
|
2314
2603
|
svg.setAttribute("viewBox", `0 0 ${pageWidth} ${pageHeight}`);
|
|
2315
2604
|
sanitizeSvgTreeForPdf(svg);
|
|
2316
2605
|
normalizeSvgViewBoxOrigin(svg);
|
|
2606
|
+
flattenNestedSvgViewports(svg);
|
|
2607
|
+
logSvgGradientClipDiagnostics("prepare:after-flatten-nested-viewports", svg, { pageKey, pageWidth, pageHeight });
|
|
2317
2608
|
disambiguateNestedSvgIds(svg);
|
|
2318
2609
|
expandSvgUseElements(svg, pageKey);
|
|
2319
2610
|
let svgToDraw = normalizeSvgExplicitColors(svg);
|
|
2611
|
+
logSvgGradientClipDiagnostics("prepare:after-explicit-colors", svgToDraw, { pageKey, pageWidth, pageHeight });
|
|
2320
2612
|
inlineComputedStyles(svgToDraw);
|
|
2321
2613
|
sanitizeSvgTreeForPdf(svgToDraw);
|
|
2322
2614
|
normalizeSvgGradientStopOffsets(svgToDraw);
|
|
2323
2615
|
expandSvgGradientHrefs(svgToDraw);
|
|
2324
2616
|
prefixSvgIds(svgToDraw, pageKey);
|
|
2617
|
+
logSvgGradientClipDiagnostics("prepare:after-gradient-normalize-prefix", svgToDraw, { pageKey, pageWidth, pageHeight });
|
|
2325
2618
|
bakeGroupOpacityIntoChildren(svgToDraw);
|
|
2326
2619
|
stripSuspiciousFullPageOverlayNodes(svgToDraw);
|
|
2327
2620
|
if (options == null ? void 0 : options.stripPageBackground) {
|
|
@@ -2343,8 +2636,10 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
|
|
|
2343
2636
|
const rewritten = rewriteSvgFontsForJsPDFWithSourceMeta(new XMLSerializer().serializeToString(svgToDraw));
|
|
2344
2637
|
const rewrittenDoc = parser.parseFromString(rewritten, "image/svg+xml");
|
|
2345
2638
|
if (!rewrittenDoc.querySelector("parsererror") && ((_a = rewrittenDoc.documentElement) == null ? void 0 : _a.tagName.toLowerCase()) === "svg") {
|
|
2639
|
+
logSvgGradientClipDiagnostics("prepare:final-before-svg2pdf", rewrittenDoc.documentElement, { pageKey, pageWidth, pageHeight, rewrittenLength: rewritten.length });
|
|
2346
2640
|
return rewrittenDoc.documentElement;
|
|
2347
2641
|
}
|
|
2642
|
+
logSvgGradientClipDiagnostics("prepare:final-before-svg2pdf", svgToDraw, { pageKey, pageWidth, pageHeight });
|
|
2348
2643
|
return svgToDraw;
|
|
2349
2644
|
} catch {
|
|
2350
2645
|
return null;
|
|
@@ -2768,7 +3063,7 @@ function resolveSvgGradientRefsToSolid(svg, elementId) {
|
|
|
2768
3063
|
function disambiguateNestedSvgIds(rootSvg) {
|
|
2769
3064
|
const nestedSvgs = Array.from(rootSvg.querySelectorAll("svg"));
|
|
2770
3065
|
const toProcess = nestedSvgs.filter((s) => s !== rootSvg);
|
|
2771
|
-
if (toProcess.length
|
|
3066
|
+
if (toProcess.length === 0) return;
|
|
2772
3067
|
toProcess.forEach((nested, idx) => {
|
|
2773
3068
|
prefixSvgIds(nested, `n${idx}`);
|
|
2774
3069
|
});
|
|
@@ -2883,6 +3178,8 @@ function normalizeSvgViewBoxOrigin(svg) {
|
|
|
2883
3178
|
if (parts.length !== 4) return;
|
|
2884
3179
|
const [vx, vy, vw, vh] = parts;
|
|
2885
3180
|
if (vw <= 0 || vh <= 0) return;
|
|
3181
|
+
svg.setAttribute("width", String(vw));
|
|
3182
|
+
svg.setAttribute("height", String(vh));
|
|
2886
3183
|
if (Math.abs(vx) < 1e-3 && Math.abs(vy) < 1e-3) return;
|
|
2887
3184
|
const doc = svg.ownerDocument;
|
|
2888
3185
|
if (!doc) return;
|
|
@@ -2955,7 +3252,7 @@ async function fetchSvgAsElement(imageUrl, colorMap) {
|
|
|
2955
3252
|
async function getRecoloredSvgDataUrl(imageUrl, colorMap) {
|
|
2956
3253
|
if (!colorMap || Object.keys(colorMap).length === 0) return null;
|
|
2957
3254
|
try {
|
|
2958
|
-
const { getNormalizedSvgUrl } = await import("./index-
|
|
3255
|
+
const { getNormalizedSvgUrl } = await import("./index-BpViFQMO.js").then((n) => n.a6);
|
|
2959
3256
|
return await getNormalizedSvgUrl(imageUrl, colorMap);
|
|
2960
3257
|
} catch {
|
|
2961
3258
|
return null;
|
|
@@ -3764,7 +4061,7 @@ async function fetchImageAsBase64(imageUrl, opts = {}) {
|
|
|
3764
4061
|
}
|
|
3765
4062
|
let fetchUrl = imageUrl;
|
|
3766
4063
|
if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
|
|
3767
|
-
const { isPrivateUrl } = await import("./index-
|
|
4064
|
+
const { isPrivateUrl } = await import("./index-BpViFQMO.js").then((n) => n.a6);
|
|
3768
4065
|
if (isPrivateUrl(imageUrl)) return null;
|
|
3769
4066
|
const proxyUrl = new URL(`${API_URL}/image-proxy`);
|
|
3770
4067
|
proxyUrl.searchParams.set("url", imageUrl);
|
|
@@ -5269,9 +5566,8 @@ async function exportFabricCanvasToVectorPdf(_fabricCanvas, options) {
|
|
|
5269
5566
|
const orientation = width > height ? "landscape" : "portrait";
|
|
5270
5567
|
const pdf = new jsPDF({
|
|
5271
5568
|
orientation,
|
|
5272
|
-
unit: "
|
|
5569
|
+
unit: "pt",
|
|
5273
5570
|
format: [width, height],
|
|
5274
|
-
hotfixes: ["px_scaling"],
|
|
5275
5571
|
compress: true
|
|
5276
5572
|
});
|
|
5277
5573
|
if (title) {
|
|
@@ -5448,9 +5744,8 @@ async function __exportMultiPagePdfInner(pages, options) {
|
|
|
5448
5744
|
const orientation = firstPage.width > firstPage.height ? "landscape" : "portrait";
|
|
5449
5745
|
const pdf = new jsPDF({
|
|
5450
5746
|
orientation,
|
|
5451
|
-
unit: "
|
|
5747
|
+
unit: "pt",
|
|
5452
5748
|
format: [firstPage.width, firstPage.height],
|
|
5453
|
-
hotfixes: ["px_scaling"],
|
|
5454
5749
|
// Always keep PDF object/stream compression on. The UI's image-compression
|
|
5455
5750
|
// switch controls raster resampling/JPEG quality, not lossless PDF packing;
|
|
5456
5751
|
// disabling this made "uncompressed image" PDFs balloon with no quality gain.
|
|
@@ -5865,4 +6160,4 @@ export {
|
|
|
5865
6160
|
preparePagesForExport,
|
|
5866
6161
|
rewriteSvgFontsForJsPDFWithSourceMeta
|
|
5867
6162
|
};
|
|
5868
|
-
//# sourceMappingURL=vectorPdfExport-
|
|
6163
|
+
//# sourceMappingURL=vectorPdfExport-D46sZGKA.js.map
|