@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.
@@ -19105,9 +19105,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
19105
19105
  }
19106
19106
  return svgString;
19107
19107
  }
19108
- const resolvedPackageVersion = "0.5.222";
19108
+ const resolvedPackageVersion = "0.5.223";
19109
19109
  const PACKAGE_VERSION = resolvedPackageVersion;
19110
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.222";
19110
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.223";
19111
19111
  const roundParityValue = (value) => {
19112
19112
  if (typeof value !== "number") return value;
19113
19113
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -19851,7 +19851,7 @@ class PixldocsRenderer {
19851
19851
  await this.waitForCanvasScene(container, cloned, i);
19852
19852
  }
19853
19853
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
19854
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-CTPOHXtQ.js");
19854
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-Cb5fFgNi.js");
19855
19855
  const prepared = preparePagesForExport(
19856
19856
  cloned.pages,
19857
19857
  canvasWidth,
@@ -21247,6 +21247,8 @@ function normalizeSvgViewBoxOrigin(svg) {
21247
21247
  if (parts.length !== 4) return;
21248
21248
  const [vx, vy, vw, vh] = parts;
21249
21249
  if (vw <= 0 || vh <= 0) return;
21250
+ svg.setAttribute("width", String(vw));
21251
+ svg.setAttribute("height", String(vh));
21250
21252
  if (Math.abs(vx) < 1e-3 && Math.abs(vy) < 1e-3) return;
21251
21253
  const doc = svg.ownerDocument;
21252
21254
  if (!doc) return;
@@ -21333,7 +21335,20 @@ function decodeSvgDataUri(href) {
21333
21335
  }
21334
21336
  }
21335
21337
  }
21336
- function inlineNestedSvgImageDataUris(svgString, domParser = new DOMParser()) {
21338
+ async function readNestedSvgImageMarkup(href) {
21339
+ if (!href) return null;
21340
+ if (href.startsWith("data:image/svg+xml")) return decodeSvgDataUri(href);
21341
+ if (!/\.svg(?:[?#]|$)/i.test(href) && !href.startsWith("blob:")) return null;
21342
+ try {
21343
+ const res = await fetch(href, { cache: "no-store" });
21344
+ if (!res.ok) return null;
21345
+ const text = await res.text();
21346
+ return /<svg[\s>]/i.test(text) ? text : null;
21347
+ } catch {
21348
+ return null;
21349
+ }
21350
+ }
21351
+ async function inlineNestedSvgImageDataUris(svgString, domParser = new DOMParser()) {
21337
21352
  var _a;
21338
21353
  try {
21339
21354
  const doc = domParser.parseFromString(svgString, "image/svg+xml");
@@ -21341,47 +21356,161 @@ function inlineNestedSvgImageDataUris(svgString, domParser = new DOMParser()) {
21341
21356
  const root = doc.documentElement;
21342
21357
  if (!root || root.tagName.toLowerCase() !== "svg") return svgString;
21343
21358
  let changed = false;
21344
- for (const img of Array.from(doc.querySelectorAll("image"))) {
21359
+ let inlined = 0;
21360
+ const images = Array.from(doc.querySelectorAll("image"));
21361
+ for (const [imageIndex, img] of images.entries()) {
21345
21362
  const href = img.getAttribute("href") || img.getAttributeNS("http://www.w3.org/1999/xlink", "href");
21346
- if (!href || !href.startsWith("data:image/svg+xml")) continue;
21363
+ if (!href) continue;
21347
21364
  try {
21348
- const svgContent = decodeSvgDataUri(href);
21365
+ const svgContent = await readNestedSvgImageMarkup(href);
21349
21366
  if (!svgContent || !/<svg[\s>]/i.test(svgContent)) continue;
21350
21367
  const innerDoc = domParser.parseFromString(svgContent, "image/svg+xml");
21351
21368
  if (innerDoc.querySelector("parsererror")) continue;
21352
21369
  const innerSvg = innerDoc.documentElement;
21353
21370
  if (!innerSvg || innerSvg.tagName.toLowerCase() !== "svg") continue;
21371
+ const sourceId = img.getAttribute("id") || `image-${imageIndex}`;
21372
+ prefixSvgIds(innerSvg, `inline-${sourceId}`);
21354
21373
  const ix = parseFloat(img.getAttribute("x") || "0") || 0;
21355
21374
  const iy = parseFloat(img.getAttribute("y") || "0") || 0;
21356
21375
  const iw = parseFloat(img.getAttribute("width") || "0");
21357
21376
  const ih = parseFloat(img.getAttribute("height") || "0");
21358
21377
  if (!(iw > 0 && ih > 0)) continue;
21359
- const nestedSvg = doc.importNode(innerSvg, true);
21360
- if (!nestedSvg.getAttribute("viewBox")) nestedSvg.setAttribute("viewBox", `0 0 ${iw} ${ih}`);
21361
- nestedSvg.setAttribute("x", "0");
21362
- nestedSvg.setAttribute("y", "0");
21363
- nestedSvg.setAttribute("width", String(iw));
21364
- nestedSvg.setAttribute("height", String(ih));
21365
- nestedSvg.setAttribute("preserveAspectRatio", img.getAttribute("preserveAspectRatio") || nestedSvg.getAttribute("preserveAspectRatio") || "xMidYMid meet");
21366
21378
  const g = doc.createElementNS("http://www.w3.org/2000/svg", "g");
21367
21379
  const existingTransform = img.getAttribute("transform") || "";
21368
- g.setAttribute("transform", `${existingTransform}${existingTransform ? " " : ""}translate(${ix},${iy})`);
21380
+ const vb = (innerSvg.getAttribute("viewBox") || "").trim().split(/[\s,]+/).map((n) => Number.parseFloat(n));
21381
+ 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];
21382
+ const par = img.getAttribute("preserveAspectRatio") || innerSvg.getAttribute("preserveAspectRatio") || "xMidYMid meet";
21383
+ const parParts = par.trim().split(/\s+/).filter(Boolean);
21384
+ const align = parParts[0] === "defer" ? parParts[1] || "xMidYMid" : parParts[0] || "xMidYMid";
21385
+ const meetOrSlice = parParts[0] === "defer" ? parParts[2] || "meet" : parParts[1] || "meet";
21386
+ let sx = iw / vbW;
21387
+ let sy = ih / vbH;
21388
+ let ox = 0;
21389
+ let oy = 0;
21390
+ if (align !== "none") {
21391
+ const s = meetOrSlice === "slice" ? Math.max(sx, sy) : Math.min(sx, sy);
21392
+ sx = s;
21393
+ sy = s;
21394
+ const extraW = iw - vbW * s;
21395
+ const extraH = ih - vbH * s;
21396
+ if (align.includes("xMid")) ox = extraW / 2;
21397
+ else if (align.includes("xMax")) ox = extraW;
21398
+ if (align.includes("YMid")) oy = extraH / 2;
21399
+ else if (align.includes("YMax")) oy = extraH;
21400
+ }
21401
+ g.setAttribute("transform", `${existingTransform}${existingTransform ? " " : ""}translate(${ix + ox},${iy + oy}) scale(${sx},${sy}) translate(${-vbX},${-vbY})`);
21402
+ const imageClipPath = img.getAttribute("clip-path") || getInlineStyleValue(img, "clip-path");
21403
+ const dropImageClipPath = isRedundantImageClipPathForInlineSvg(root, imageClipPath, ix, iy, iw, ih);
21404
+ if (dropImageClipPath) console.log("[canvas-renderer][pdf] dropped redundant inline SVG image clipPath", { imageIndex, sourceId, clipPath: imageClipPath });
21369
21405
  for (const attr of Array.from(img.attributes)) {
21406
+ if (dropImageClipPath && attr.name === "clip-path") continue;
21407
+ if (dropImageClipPath && attr.name === "style") {
21408
+ const kept = parseInlineSvgStyleDeclarations(attr.value).filter((decl) => decl.key !== "clip-path").map((decl) => `${decl.key}: ${decl.value}`).join("; ");
21409
+ if (kept) g.setAttribute("style", kept);
21410
+ continue;
21411
+ }
21370
21412
  if (["id", "class", "style", "opacity", "display", "visibility", "clip-path", "mask", "filter", "pointer-events"].includes(attr.name)) {
21371
21413
  g.setAttribute(attr.name, attr.value);
21372
21414
  }
21373
21415
  }
21374
- g.appendChild(nestedSvg);
21416
+ for (const child of Array.from(innerSvg.childNodes)) g.appendChild(doc.importNode(child, true));
21375
21417
  (_a = img.parentNode) == null ? void 0 : _a.replaceChild(g, img);
21376
21418
  changed = true;
21419
+ inlined++;
21377
21420
  } catch {
21378
21421
  }
21379
21422
  }
21423
+ if (inlined > 0) console.log(`[canvas-renderer][pdf] inlined ${inlined} nested SVG image(s)`);
21380
21424
  return changed ? new XMLSerializer().serializeToString(doc.documentElement) : svgString;
21381
21425
  } catch {
21382
21426
  return svgString;
21383
21427
  }
21384
21428
  }
21429
+ function parseSvgLength(value, fallback) {
21430
+ if (!value) return fallback;
21431
+ const trimmed = value.trim();
21432
+ if (!trimmed || trimmed.endsWith("%")) return fallback;
21433
+ const parsed = Number.parseFloat(trimmed);
21434
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
21435
+ }
21436
+ function isIdentitySvgMatrix(value) {
21437
+ var _a;
21438
+ if (!value) return true;
21439
+ const nums = ((_a = value.match(/-?\d*\.?\d+(?:e[-+]?\d+)?/gi)) == null ? void 0 : _a.map(Number)) ?? [];
21440
+ 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;
21441
+ }
21442
+ function isRedundantImageClipPathForInlineSvg(root, clipPathRef, ix, iy, iw, ih) {
21443
+ const clipId = extractGradientIdFromPaint(clipPathRef);
21444
+ if (!clipId || !(iw > 0 && ih > 0)) return false;
21445
+ let clip = null;
21446
+ for (const el of root.querySelectorAll("[id]")) {
21447
+ if (el.getAttribute("id") === clipId) {
21448
+ clip = el;
21449
+ break;
21450
+ }
21451
+ }
21452
+ if (!clip || clip.tagName.toLowerCase() !== "clippath") return false;
21453
+ const units = (clip.getAttribute("clipPathUnits") || "userSpaceOnUse").toLowerCase();
21454
+ const rects = Array.from(clip.children).filter((el) => el.tagName.toLowerCase() === "rect");
21455
+ if (rects.length !== 1 || rects[0].parentElement !== clip) return false;
21456
+ const rect = rects[0];
21457
+ if (!isIdentitySvgMatrix(rect.getAttribute("transform"))) return false;
21458
+ const rx = Number.parseFloat(rect.getAttribute("x") || "0") || 0;
21459
+ const ry = Number.parseFloat(rect.getAttribute("y") || "0") || 0;
21460
+ const rw = Number.parseFloat(rect.getAttribute("width") || "0");
21461
+ const rh = Number.parseFloat(rect.getAttribute("height") || "0");
21462
+ if (!(rw > 0 && rh > 0)) return false;
21463
+ const near = (a, b) => Math.abs(a - b) <= Math.max(1, Math.max(Math.abs(a), Math.abs(b)) * 0.01);
21464
+ if (units === "objectboundingbox") return near(rx, 0) && near(ry, 0) && near(rw, 1) && near(rh, 1);
21465
+ return near(rx, ix) && near(ry, iy) && near(rw, iw) && near(rh, ih);
21466
+ }
21467
+ function flattenNestedSvgViewports(rootSvg) {
21468
+ const nestedSvgs = Array.from(rootSvg.querySelectorAll("svg")).filter((svg) => svg !== rootSvg).reverse();
21469
+ let flattened = 0;
21470
+ for (const nestedSvg of nestedSvgs) {
21471
+ const parent = nestedSvg.parentNode;
21472
+ const doc = nestedSvg.ownerDocument;
21473
+ if (!parent || !doc) continue;
21474
+ const vb = (nestedSvg.getAttribute("viewBox") || "").trim().split(/[\s,]+/).map((n) => Number.parseFloat(n));
21475
+ const hasViewBox = vb.length === 4 && vb.every((n) => Number.isFinite(n)) && vb[2] > 0 && vb[3] > 0;
21476
+ const [vbX, vbY, vbW, vbH] = hasViewBox ? vb : [0, 0, 0, 0];
21477
+ const width = parseSvgLength(nestedSvg.getAttribute("width"), hasViewBox ? vbW : 0);
21478
+ const height = parseSvgLength(nestedSvg.getAttribute("height"), hasViewBox ? vbH : 0);
21479
+ if (!(width > 0 && height > 0)) continue;
21480
+ const x = Number.parseFloat(nestedSvg.getAttribute("x") || "0") || 0;
21481
+ const y = Number.parseFloat(nestedSvg.getAttribute("y") || "0") || 0;
21482
+ const par = nestedSvg.getAttribute("preserveAspectRatio") || "xMidYMid meet";
21483
+ const parParts = par.trim().split(/\s+/).filter(Boolean);
21484
+ const align = parParts[0] === "defer" ? parParts[1] || "xMidYMid" : parParts[0] || "xMidYMid";
21485
+ const meetOrSlice = parParts[0] === "defer" ? parParts[2] || "meet" : parParts[1] || "meet";
21486
+ let sx = hasViewBox ? width / vbW : 1;
21487
+ let sy = hasViewBox ? height / vbH : 1;
21488
+ let ox = 0;
21489
+ let oy = 0;
21490
+ if (hasViewBox && align !== "none") {
21491
+ const s = meetOrSlice === "slice" ? Math.max(sx, sy) : Math.min(sx, sy);
21492
+ sx = s;
21493
+ sy = s;
21494
+ const extraW = width - vbW * s;
21495
+ const extraH = height - vbH * s;
21496
+ if (align.includes("xMid")) ox = extraW / 2;
21497
+ else if (align.includes("xMax")) ox = extraW;
21498
+ if (align.includes("YMid")) oy = extraH / 2;
21499
+ else if (align.includes("YMax")) oy = extraH;
21500
+ }
21501
+ const g = doc.createElementNS("http://www.w3.org/2000/svg", "g");
21502
+ const existingTransform = nestedSvg.getAttribute("transform") || "";
21503
+ const viewBoxTransform = hasViewBox ? `translate(${x + ox},${y + oy}) scale(${sx},${sy}) translate(${-vbX},${-vbY})` : `translate(${x},${y})`;
21504
+ g.setAttribute("transform", `${existingTransform}${existingTransform ? " " : ""}${viewBoxTransform}`);
21505
+ for (const attr of Array.from(nestedSvg.attributes)) {
21506
+ if (["id", "class", "style", "opacity", "display", "visibility", "clip-path", "mask", "filter", "pointer-events"].includes(attr.name)) g.setAttribute(attr.name, attr.value);
21507
+ }
21508
+ for (const child of Array.from(nestedSvg.childNodes)) g.appendChild(child);
21509
+ parent.replaceChild(g, nestedSvg);
21510
+ flattened++;
21511
+ }
21512
+ if (flattened > 0) console.log("[canvas-renderer][pdf] flattened nested SVG viewport(s)", { flattened });
21513
+ }
21385
21514
  function inlineComputedStyles(svg) {
21386
21515
  if (typeof document === "undefined") return;
21387
21516
  const wrap = document.createElement("div");
@@ -21422,7 +21551,7 @@ function inlineComputedStyles(svg) {
21422
21551
  function disambiguateNestedSvgIds(rootSvg) {
21423
21552
  const nestedSvgs = Array.from(rootSvg.querySelectorAll("svg"));
21424
21553
  const toProcess = nestedSvgs.filter((s) => s !== rootSvg);
21425
- if (toProcess.length < 2) return;
21554
+ if (toProcess.length === 0) return;
21426
21555
  toProcess.forEach((nested, idx) => {
21427
21556
  prefixSvgIds(nested, `n${idx}`);
21428
21557
  });
@@ -22014,7 +22143,7 @@ function restoreSourceFontsForShadowRaster(markup) {
22014
22143
  async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
22015
22144
  try {
22016
22145
  const parser = new DOMParser();
22017
- const processedSvg = inlineNestedSvgImageDataUris(rawSvg, parser);
22146
+ const processedSvg = await inlineNestedSvgImageDataUris(rawSvg, parser);
22018
22147
  const doc = parser.parseFromString(processedSvg, "image/svg+xml");
22019
22148
  if (doc.querySelector("parsererror")) return null;
22020
22149
  const svg = doc.documentElement;
@@ -22028,6 +22157,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
22028
22157
  svg.setAttribute("viewBox", `0 0 ${pageWidth} ${pageHeight}`);
22029
22158
  sanitizeSvgTreeForPdf(svg);
22030
22159
  normalizeSvgViewBoxOrigin(svg);
22160
+ flattenNestedSvgViewports(svg);
22031
22161
  disambiguateNestedSvgIds(svg);
22032
22162
  expandSvgUseElements(svg);
22033
22163
  const svgToDraw = normalizeSvgExplicitColors(svg);
@@ -22041,7 +22171,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
22041
22171
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
22042
22172
  sanitizeSvgTreeForPdf(svgToDraw);
22043
22173
  try {
22044
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-CTPOHXtQ.js");
22174
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-Cb5fFgNi.js");
22045
22175
  try {
22046
22176
  await logTextMeasurementDiagnostic(svgToDraw);
22047
22177
  } catch {
@@ -22141,9 +22271,8 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
22141
22271
  }
22142
22272
  const pdf = new jsPDF({
22143
22273
  orientation,
22144
- unit: "px",
22274
+ unit: "pt",
22145
22275
  format: [firstPage.width, firstPage.height],
22146
- hotfixes: ["px_scaling"],
22147
22276
  compress: true
22148
22277
  });
22149
22278
  if (title) pdf.setProperties({ title, creator: "Pixldocs" });
@@ -22441,4 +22570,4 @@ export {
22441
22570
  buildTeaserBlurFlatKeys as y,
22442
22571
  collectFontDescriptorsFromConfig as z
22443
22572
  };
22444
- //# sourceMappingURL=index-DxL--cfL.js.map
22573
+ //# sourceMappingURL=index-DXJtHKQO.js.map