@pixldocs/canvas-renderer 0.5.46 → 0.5.47

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.cjs CHANGED
@@ -4938,35 +4938,31 @@ function applyTextBackground(obj, cfg) {
4938
4938
  const bgPath = hasBg ? `<path d="${bgD}" fill="${escapeXmlAttr(bgFill)}" />` : "";
4939
4939
  svg = svg.replace(/style="[^"]*filter:\s*url\([^)]+\)[^"]*"/i, "");
4940
4940
  svg = svg.replace(/<filter[\s\S]*?<\/filter>/gi, "");
4941
- let bgShadowLayer = "";
4942
- let textShadowLayer = "";
4941
+ let bgShadowMarker = "";
4942
+ let textShadowMarker = "";
4943
4943
  if (hasShadow) {
4944
4944
  const ox = Number(shadow.offsetX ?? 0) || 0;
4945
4945
  const oy = Number(shadow.offsetY ?? 0) || 0;
4946
4946
  const blur = Math.max(0, Number(shadow.blur ?? 0));
4947
4947
  const shadowColor = String(shadow.color);
4948
- const shadowBgPath = hasBg ? `<path d="${bgD}" fill="${escapeXmlAttr(shadowColor)}" />` : "";
4948
+ const pad = Math.max(8, Math.ceil(blur * 3) + Math.ceil(Math.max(Math.abs(ox), Math.abs(oy))) + 4);
4949
+ const bx = -w / 2 - pL - pad;
4950
+ const by = -h / 2 - pT - pad;
4951
+ const bw = w + pL + pR + pad * 2;
4952
+ const bh = h + pT + pB + pad * 2;
4953
+ const dataAttrs = `data-blur="${blur.toFixed(3)}" data-ox="${ox.toFixed(3)}" data-oy="${oy.toFixed(3)}" data-bx="${bx.toFixed(3)}" data-by="${by.toFixed(3)}" data-bw="${bw.toFixed(3)}" data-bh="${bh.toFixed(3)}" data-color="${escapeXmlAttr(shadowColor)}"`;
4954
+ if (hasBg) {
4955
+ const shadowBgPath = `<path d="${bgD}" fill="${escapeXmlAttr(shadowColor)}" />`;
4956
+ bgShadowMarker = `<g class="__pdShadowRaster" ${dataAttrs}>${shadowBgPath}</g>`;
4957
+ }
4949
4958
  const inner = extractGInnerMarkup(svg);
4950
4959
  const recoloredText = recolorSvgFills(inner, shadowColor);
4951
- const bgLayers = [];
4952
- const textLayers = [];
4953
- const pushShadowPass = (tx, ty, opacity) => {
4954
- const attrs = `transform="translate(${tx.toFixed(3)} ${ty.toFixed(3)})"${opacity ? ` opacity="${opacity}"` : ""}`;
4955
- if (shadowBgPath) bgLayers.push(`<g ${attrs}>${shadowBgPath}</g>`);
4956
- if (recoloredText) textLayers.push(`<g ${attrs}>${recoloredText}</g>`);
4957
- };
4958
- if (blur > 0) {
4959
- for (const pass of buildNormalizedShadowPasses(blur)) {
4960
- pushShadowPass(ox + pass.dx, oy + pass.dy, pass.opacity);
4961
- }
4962
- } else {
4963
- pushShadowPass(ox, oy);
4960
+ if (recoloredText) {
4961
+ textShadowMarker = `<g class="__pdShadowRaster" ${dataAttrs}>${recoloredText}</g>`;
4964
4962
  }
4965
- bgShadowLayer = bgLayers.join("");
4966
- textShadowLayer = textLayers.join("");
4967
4963
  }
4968
4964
  const openTagMatch = svg.match(/^\s*<g\b[^>]*>/);
4969
- const inserted = bgShadowLayer + bgPath + textShadowLayer;
4965
+ const inserted = bgShadowMarker + bgPath + textShadowMarker;
4970
4966
  if (openTagMatch) {
4971
4967
  const openTag = openTagMatch[0];
4972
4968
  return svg.replace(openTag, openTag + inserted);
@@ -4994,36 +4990,6 @@ function recolorSvgFills(svg, color) {
4994
4990
  );
4995
4991
  return out;
4996
4992
  }
4997
- function buildNormalizedShadowPasses(blur) {
4998
- const safeBlur = Math.max(0, Number(blur) || 0);
4999
- const ringCount = Math.min(5, Math.max(2, Math.round(safeBlur / 5)));
5000
- const targetOpacity = Math.max(0.16, Math.min(0.38, 0.46 - safeBlur * 0.01));
5001
- const weighted = [
5002
- { dx: 0, dy: 0, weight: 0.7 }
5003
- ];
5004
- for (let i = 1; i <= ringCount; i++) {
5005
- const t = i / ringCount;
5006
- const dist = safeBlur * t * 0.42;
5007
- const weight = Math.exp(-2.4 * t * t);
5008
- const diag = dist * 0.7071;
5009
- weighted.push(
5010
- { dx: dist, dy: 0, weight },
5011
- { dx: -dist, dy: 0, weight },
5012
- { dx: 0, dy: dist, weight },
5013
- { dx: 0, dy: -dist, weight },
5014
- { dx: diag, dy: diag, weight: weight * 0.72 },
5015
- { dx: -diag, dy: diag, weight: weight * 0.72 },
5016
- { dx: diag, dy: -diag, weight: weight * 0.72 },
5017
- { dx: -diag, dy: -diag, weight: weight * 0.72 }
5018
- );
5019
- }
5020
- const totalWeight = weighted.reduce((sum, pass) => sum + pass.weight, 0) || 1;
5021
- return weighted.map((pass) => ({
5022
- dx: pass.dx,
5023
- dy: pass.dy,
5024
- opacity: Math.max(3e-3, targetOpacity * pass.weight / totalWeight).toFixed(4)
5025
- }));
5026
- }
5027
4993
  function buildRoundedRectPathD(x, y, w, h, rTL, rTR, rBR, rBL) {
5028
4994
  const maxR = Math.min(w, h) / 2;
5029
4995
  const tl = Math.min(Math.max(0, rTL), maxR);
@@ -12375,7 +12341,7 @@ function PixldocsPreview(props) {
12375
12341
  !canvasSettled && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
12376
12342
  ] });
12377
12343
  }
12378
- const PACKAGE_VERSION = "0.5.46";
12344
+ const PACKAGE_VERSION = "0.5.47";
12379
12345
  let __underlineFixInstalled = false;
12380
12346
  function installUnderlineFix(fab) {
12381
12347
  var _a;
@@ -15041,6 +15007,130 @@ async function convertTextDecorationsToLines(svg) {
15041
15007
  }
15042
15008
  }
15043
15009
  }
15010
+ async function rasterizeShadowMarkers(svg) {
15011
+ var _a, _b, _c, _d, _e;
15012
+ if (typeof window === "undefined" || typeof document === "undefined") return;
15013
+ const markers = Array.from(svg.querySelectorAll("g.__pdShadowRaster"));
15014
+ if (markers.length === 0) return;
15015
+ const SVG_NS = "http://www.w3.org/2000/svg";
15016
+ const XLINK_NS = "http://www.w3.org/1999/xlink";
15017
+ try {
15018
+ if ((_a = document.fonts) == null ? void 0 : _a.ready) await document.fonts.ready;
15019
+ } catch {
15020
+ }
15021
+ const fontFaceCss = collectDocumentFontFaceCss();
15022
+ for (const marker of markers) {
15023
+ try {
15024
+ const blur = parseFloat(marker.getAttribute("data-blur") || "0");
15025
+ const ox = parseFloat(marker.getAttribute("data-ox") || "0");
15026
+ const oy = parseFloat(marker.getAttribute("data-oy") || "0");
15027
+ const bx = parseFloat(marker.getAttribute("data-bx") || "0");
15028
+ const by = parseFloat(marker.getAttribute("data-by") || "0");
15029
+ const bw = parseFloat(marker.getAttribute("data-bw") || "0");
15030
+ const bh = parseFloat(marker.getAttribute("data-bh") || "0");
15031
+ if (!Number.isFinite(bw) || !Number.isFinite(bh) || bw <= 0 || bh <= 0) {
15032
+ (_b = marker.parentNode) == null ? void 0 : _b.removeChild(marker);
15033
+ continue;
15034
+ }
15035
+ const innerXml = Array.from(marker.childNodes).map((n) => n instanceof Element ? new XMLSerializer().serializeToString(n) : "").join("");
15036
+ const scale = 2;
15037
+ const pxW = Math.min(4096, Math.max(8, Math.ceil(bw * scale)));
15038
+ const pxH = Math.min(4096, Math.max(8, Math.ceil(bh * scale)));
15039
+ const stdDev = Math.max(0, blur / 2);
15040
+ const filterId = `pdShadowBlur_${Math.random().toString(36).slice(2, 9)}`;
15041
+ const styleBlock = fontFaceCss ? `<style>${fontFaceCss}</style>` : "";
15042
+ const miniSvg = `<svg xmlns="${SVG_NS}" xmlns:xlink="${XLINK_NS}" width="${pxW}" height="${pxH}" viewBox="${bx} ${by} ${bw} ${bh}"><defs>${styleBlock}<filter id="${filterId}" x="-50%" y="-50%" width="200%" height="200%" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="${stdDev}" /></filter></defs><g filter="url(#${filterId})">${innerXml}</g></svg>`;
15043
+ const dataUrl = await rasterSvgToPngDataUrl(miniSvg, pxW, pxH);
15044
+ if (!dataUrl) {
15045
+ (_c = marker.parentNode) == null ? void 0 : _c.removeChild(marker);
15046
+ continue;
15047
+ }
15048
+ const img = svg.ownerDocument.createElementNS(SVG_NS, "image");
15049
+ img.setAttribute("x", String(bx + ox));
15050
+ img.setAttribute("y", String(by + oy));
15051
+ img.setAttribute("width", String(bw));
15052
+ img.setAttribute("height", String(bh));
15053
+ img.setAttribute("preserveAspectRatio", "none");
15054
+ img.setAttributeNS(XLINK_NS, "xlink:href", dataUrl);
15055
+ img.setAttribute("href", dataUrl);
15056
+ (_d = marker.parentNode) == null ? void 0 : _d.replaceChild(img, marker);
15057
+ } catch (e) {
15058
+ console.warn("[pdf-export] rasterizeShadowMarkers failed for one marker:", e);
15059
+ try {
15060
+ (_e = marker.parentNode) == null ? void 0 : _e.removeChild(marker);
15061
+ } catch {
15062
+ }
15063
+ }
15064
+ }
15065
+ }
15066
+ function rasterSvgToPngDataUrl(svgMarkup, pxW, pxH) {
15067
+ return new Promise((resolve) => {
15068
+ try {
15069
+ const blob = new Blob([svgMarkup], { type: "image/svg+xml;charset=utf-8" });
15070
+ const url = URL.createObjectURL(blob);
15071
+ const img = new Image();
15072
+ img.crossOrigin = "anonymous";
15073
+ const cleanup = () => {
15074
+ try {
15075
+ URL.revokeObjectURL(url);
15076
+ } catch {
15077
+ }
15078
+ };
15079
+ img.onload = () => {
15080
+ try {
15081
+ const canvas = document.createElement("canvas");
15082
+ canvas.width = pxW;
15083
+ canvas.height = pxH;
15084
+ const ctx = canvas.getContext("2d");
15085
+ if (!ctx) {
15086
+ cleanup();
15087
+ resolve(null);
15088
+ return;
15089
+ }
15090
+ ctx.drawImage(img, 0, 0, pxW, pxH);
15091
+ const dataUrl = canvas.toDataURL("image/png");
15092
+ cleanup();
15093
+ resolve(dataUrl);
15094
+ } catch (e) {
15095
+ cleanup();
15096
+ resolve(null);
15097
+ }
15098
+ };
15099
+ img.onerror = () => {
15100
+ cleanup();
15101
+ resolve(null);
15102
+ };
15103
+ img.src = url;
15104
+ } catch {
15105
+ resolve(null);
15106
+ }
15107
+ });
15108
+ }
15109
+ let cachedFontFaceCss = null;
15110
+ function collectDocumentFontFaceCss() {
15111
+ if (cachedFontFaceCss !== null) return cachedFontFaceCss;
15112
+ const out = [];
15113
+ try {
15114
+ for (const sheet of Array.from(document.styleSheets)) {
15115
+ let rules = null;
15116
+ try {
15117
+ rules = sheet.cssRules;
15118
+ } catch {
15119
+ rules = null;
15120
+ }
15121
+ if (!rules) continue;
15122
+ for (const rule of Array.from(rules)) {
15123
+ const r = rule;
15124
+ if (r && (r.type === 5 || /@font-face/i.test(r.cssText || ""))) {
15125
+ if (r.cssText) out.push(r.cssText);
15126
+ }
15127
+ }
15128
+ }
15129
+ } catch {
15130
+ }
15131
+ cachedFontFaceCss = out.join("\n");
15132
+ return cachedFontFaceCss;
15133
+ }
15044
15134
  async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
15045
15135
  try {
15046
15136
  const parser = new DOMParser();
@@ -15070,6 +15160,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
15070
15160
  stripSuspiciousFullPageOverlayNodes(svgToDraw);
15071
15161
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
15072
15162
  sanitizeSvgTreeForPdf(svgToDraw);
15163
+ await rasterizeShadowMarkers(svgToDraw);
15073
15164
  return svgToDraw;
15074
15165
  } catch {
15075
15166
  return null;