@pixldocs/canvas-renderer 0.5.222 → 0.5.223

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.
@@ -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-DxL--cfL.js";
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-DXJtHKQO.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 inlineNestedSvgImageDataUris(svgString, domParser = new DOMParser()) {
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
- for (const img of images) {
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 || !href.startsWith("data:image/svg+xml")) continue;
1490
+ if (!href) continue;
1362
1491
  try {
1363
- const svgContent = decodeSvgDataUri(href);
1364
- if (!svgContent || !/<svg[\s>]/i.test(svgContent)) continue;
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)) continue;
1374
- const nestedSvg = doc.importNode(innerSvg, true);
1375
- const vb = nestedSvg.getAttribute("viewBox");
1376
- if (!vb) {
1377
- nestedSvg.setAttribute("viewBox", `0 0 ${iw} ${ih}`);
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
- g.appendChild(nestedSvg);
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 < 2) return;
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-DxL--cfL.js").then((n) => n.a6);
3255
+ const { getNormalizedSvgUrl } = await import("./index-DXJtHKQO.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-DxL--cfL.js").then((n) => n.a6);
4064
+ const { isPrivateUrl } = await import("./index-DXJtHKQO.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: "px",
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: "px",
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-CTPOHXtQ.js.map
6163
+ //# sourceMappingURL=vectorPdfExport-Cb5fFgNi.js.map