@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.d.ts CHANGED
@@ -221,7 +221,7 @@ export declare function normalizeFontFamily(fontStack: string): string;
221
221
  * Package version banner. Bump alongside package.json so we can confirm
222
222
  * (via browser:log) that the deployed bundle matches the expected build.
223
223
  */
224
- export declare const PACKAGE_VERSION = "0.5.46";
224
+ export declare const PACKAGE_VERSION = "0.5.47";
225
225
 
226
226
  export declare interface PageSettings {
227
227
  backgroundColor?: string;
package/dist/index.js CHANGED
@@ -4919,35 +4919,31 @@ function applyTextBackground(obj, cfg) {
4919
4919
  const bgPath = hasBg ? `<path d="${bgD}" fill="${escapeXmlAttr(bgFill)}" />` : "";
4920
4920
  svg = svg.replace(/style="[^"]*filter:\s*url\([^)]+\)[^"]*"/i, "");
4921
4921
  svg = svg.replace(/<filter[\s\S]*?<\/filter>/gi, "");
4922
- let bgShadowLayer = "";
4923
- let textShadowLayer = "";
4922
+ let bgShadowMarker = "";
4923
+ let textShadowMarker = "";
4924
4924
  if (hasShadow) {
4925
4925
  const ox = Number(shadow.offsetX ?? 0) || 0;
4926
4926
  const oy = Number(shadow.offsetY ?? 0) || 0;
4927
4927
  const blur = Math.max(0, Number(shadow.blur ?? 0));
4928
4928
  const shadowColor = String(shadow.color);
4929
- const shadowBgPath = hasBg ? `<path d="${bgD}" fill="${escapeXmlAttr(shadowColor)}" />` : "";
4929
+ const pad = Math.max(8, Math.ceil(blur * 3) + Math.ceil(Math.max(Math.abs(ox), Math.abs(oy))) + 4);
4930
+ const bx = -w / 2 - pL - pad;
4931
+ const by = -h / 2 - pT - pad;
4932
+ const bw = w + pL + pR + pad * 2;
4933
+ const bh = h + pT + pB + pad * 2;
4934
+ 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)}"`;
4935
+ if (hasBg) {
4936
+ const shadowBgPath = `<path d="${bgD}" fill="${escapeXmlAttr(shadowColor)}" />`;
4937
+ bgShadowMarker = `<g class="__pdShadowRaster" ${dataAttrs}>${shadowBgPath}</g>`;
4938
+ }
4930
4939
  const inner = extractGInnerMarkup(svg);
4931
4940
  const recoloredText = recolorSvgFills(inner, shadowColor);
4932
- const bgLayers = [];
4933
- const textLayers = [];
4934
- const pushShadowPass = (tx, ty, opacity) => {
4935
- const attrs = `transform="translate(${tx.toFixed(3)} ${ty.toFixed(3)})"${opacity ? ` opacity="${opacity}"` : ""}`;
4936
- if (shadowBgPath) bgLayers.push(`<g ${attrs}>${shadowBgPath}</g>`);
4937
- if (recoloredText) textLayers.push(`<g ${attrs}>${recoloredText}</g>`);
4938
- };
4939
- if (blur > 0) {
4940
- for (const pass of buildNormalizedShadowPasses(blur)) {
4941
- pushShadowPass(ox + pass.dx, oy + pass.dy, pass.opacity);
4942
- }
4943
- } else {
4944
- pushShadowPass(ox, oy);
4941
+ if (recoloredText) {
4942
+ textShadowMarker = `<g class="__pdShadowRaster" ${dataAttrs}>${recoloredText}</g>`;
4945
4943
  }
4946
- bgShadowLayer = bgLayers.join("");
4947
- textShadowLayer = textLayers.join("");
4948
4944
  }
4949
4945
  const openTagMatch = svg.match(/^\s*<g\b[^>]*>/);
4950
- const inserted = bgShadowLayer + bgPath + textShadowLayer;
4946
+ const inserted = bgShadowMarker + bgPath + textShadowMarker;
4951
4947
  if (openTagMatch) {
4952
4948
  const openTag = openTagMatch[0];
4953
4949
  return svg.replace(openTag, openTag + inserted);
@@ -4975,36 +4971,6 @@ function recolorSvgFills(svg, color) {
4975
4971
  );
4976
4972
  return out;
4977
4973
  }
4978
- function buildNormalizedShadowPasses(blur) {
4979
- const safeBlur = Math.max(0, Number(blur) || 0);
4980
- const ringCount = Math.min(5, Math.max(2, Math.round(safeBlur / 5)));
4981
- const targetOpacity = Math.max(0.16, Math.min(0.38, 0.46 - safeBlur * 0.01));
4982
- const weighted = [
4983
- { dx: 0, dy: 0, weight: 0.7 }
4984
- ];
4985
- for (let i = 1; i <= ringCount; i++) {
4986
- const t = i / ringCount;
4987
- const dist = safeBlur * t * 0.42;
4988
- const weight = Math.exp(-2.4 * t * t);
4989
- const diag = dist * 0.7071;
4990
- weighted.push(
4991
- { dx: dist, dy: 0, weight },
4992
- { dx: -dist, dy: 0, weight },
4993
- { dx: 0, dy: dist, weight },
4994
- { dx: 0, dy: -dist, weight },
4995
- { dx: diag, dy: diag, weight: weight * 0.72 },
4996
- { dx: -diag, dy: diag, weight: weight * 0.72 },
4997
- { dx: diag, dy: -diag, weight: weight * 0.72 },
4998
- { dx: -diag, dy: -diag, weight: weight * 0.72 }
4999
- );
5000
- }
5001
- const totalWeight = weighted.reduce((sum, pass) => sum + pass.weight, 0) || 1;
5002
- return weighted.map((pass) => ({
5003
- dx: pass.dx,
5004
- dy: pass.dy,
5005
- opacity: Math.max(3e-3, targetOpacity * pass.weight / totalWeight).toFixed(4)
5006
- }));
5007
- }
5008
4974
  function buildRoundedRectPathD(x, y, w, h, rTL, rTR, rBR, rBL) {
5009
4975
  const maxR = Math.min(w, h) / 2;
5010
4976
  const tl = Math.min(Math.max(0, rTL), maxR);
@@ -12356,7 +12322,7 @@ function PixldocsPreview(props) {
12356
12322
  !canvasSettled && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
12357
12323
  ] });
12358
12324
  }
12359
- const PACKAGE_VERSION = "0.5.46";
12325
+ const PACKAGE_VERSION = "0.5.47";
12360
12326
  let __underlineFixInstalled = false;
12361
12327
  function installUnderlineFix(fab) {
12362
12328
  var _a;
@@ -15022,6 +14988,130 @@ async function convertTextDecorationsToLines(svg) {
15022
14988
  }
15023
14989
  }
15024
14990
  }
14991
+ async function rasterizeShadowMarkers(svg) {
14992
+ var _a, _b, _c, _d, _e;
14993
+ if (typeof window === "undefined" || typeof document === "undefined") return;
14994
+ const markers = Array.from(svg.querySelectorAll("g.__pdShadowRaster"));
14995
+ if (markers.length === 0) return;
14996
+ const SVG_NS = "http://www.w3.org/2000/svg";
14997
+ const XLINK_NS = "http://www.w3.org/1999/xlink";
14998
+ try {
14999
+ if ((_a = document.fonts) == null ? void 0 : _a.ready) await document.fonts.ready;
15000
+ } catch {
15001
+ }
15002
+ const fontFaceCss = collectDocumentFontFaceCss();
15003
+ for (const marker of markers) {
15004
+ try {
15005
+ const blur = parseFloat(marker.getAttribute("data-blur") || "0");
15006
+ const ox = parseFloat(marker.getAttribute("data-ox") || "0");
15007
+ const oy = parseFloat(marker.getAttribute("data-oy") || "0");
15008
+ const bx = parseFloat(marker.getAttribute("data-bx") || "0");
15009
+ const by = parseFloat(marker.getAttribute("data-by") || "0");
15010
+ const bw = parseFloat(marker.getAttribute("data-bw") || "0");
15011
+ const bh = parseFloat(marker.getAttribute("data-bh") || "0");
15012
+ if (!Number.isFinite(bw) || !Number.isFinite(bh) || bw <= 0 || bh <= 0) {
15013
+ (_b = marker.parentNode) == null ? void 0 : _b.removeChild(marker);
15014
+ continue;
15015
+ }
15016
+ const innerXml = Array.from(marker.childNodes).map((n) => n instanceof Element ? new XMLSerializer().serializeToString(n) : "").join("");
15017
+ const scale = 2;
15018
+ const pxW = Math.min(4096, Math.max(8, Math.ceil(bw * scale)));
15019
+ const pxH = Math.min(4096, Math.max(8, Math.ceil(bh * scale)));
15020
+ const stdDev = Math.max(0, blur / 2);
15021
+ const filterId = `pdShadowBlur_${Math.random().toString(36).slice(2, 9)}`;
15022
+ const styleBlock = fontFaceCss ? `<style>${fontFaceCss}</style>` : "";
15023
+ 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>`;
15024
+ const dataUrl = await rasterSvgToPngDataUrl(miniSvg, pxW, pxH);
15025
+ if (!dataUrl) {
15026
+ (_c = marker.parentNode) == null ? void 0 : _c.removeChild(marker);
15027
+ continue;
15028
+ }
15029
+ const img = svg.ownerDocument.createElementNS(SVG_NS, "image");
15030
+ img.setAttribute("x", String(bx + ox));
15031
+ img.setAttribute("y", String(by + oy));
15032
+ img.setAttribute("width", String(bw));
15033
+ img.setAttribute("height", String(bh));
15034
+ img.setAttribute("preserveAspectRatio", "none");
15035
+ img.setAttributeNS(XLINK_NS, "xlink:href", dataUrl);
15036
+ img.setAttribute("href", dataUrl);
15037
+ (_d = marker.parentNode) == null ? void 0 : _d.replaceChild(img, marker);
15038
+ } catch (e) {
15039
+ console.warn("[pdf-export] rasterizeShadowMarkers failed for one marker:", e);
15040
+ try {
15041
+ (_e = marker.parentNode) == null ? void 0 : _e.removeChild(marker);
15042
+ } catch {
15043
+ }
15044
+ }
15045
+ }
15046
+ }
15047
+ function rasterSvgToPngDataUrl(svgMarkup, pxW, pxH) {
15048
+ return new Promise((resolve) => {
15049
+ try {
15050
+ const blob = new Blob([svgMarkup], { type: "image/svg+xml;charset=utf-8" });
15051
+ const url = URL.createObjectURL(blob);
15052
+ const img = new Image();
15053
+ img.crossOrigin = "anonymous";
15054
+ const cleanup = () => {
15055
+ try {
15056
+ URL.revokeObjectURL(url);
15057
+ } catch {
15058
+ }
15059
+ };
15060
+ img.onload = () => {
15061
+ try {
15062
+ const canvas = document.createElement("canvas");
15063
+ canvas.width = pxW;
15064
+ canvas.height = pxH;
15065
+ const ctx = canvas.getContext("2d");
15066
+ if (!ctx) {
15067
+ cleanup();
15068
+ resolve(null);
15069
+ return;
15070
+ }
15071
+ ctx.drawImage(img, 0, 0, pxW, pxH);
15072
+ const dataUrl = canvas.toDataURL("image/png");
15073
+ cleanup();
15074
+ resolve(dataUrl);
15075
+ } catch (e) {
15076
+ cleanup();
15077
+ resolve(null);
15078
+ }
15079
+ };
15080
+ img.onerror = () => {
15081
+ cleanup();
15082
+ resolve(null);
15083
+ };
15084
+ img.src = url;
15085
+ } catch {
15086
+ resolve(null);
15087
+ }
15088
+ });
15089
+ }
15090
+ let cachedFontFaceCss = null;
15091
+ function collectDocumentFontFaceCss() {
15092
+ if (cachedFontFaceCss !== null) return cachedFontFaceCss;
15093
+ const out = [];
15094
+ try {
15095
+ for (const sheet of Array.from(document.styleSheets)) {
15096
+ let rules = null;
15097
+ try {
15098
+ rules = sheet.cssRules;
15099
+ } catch {
15100
+ rules = null;
15101
+ }
15102
+ if (!rules) continue;
15103
+ for (const rule of Array.from(rules)) {
15104
+ const r = rule;
15105
+ if (r && (r.type === 5 || /@font-face/i.test(r.cssText || ""))) {
15106
+ if (r.cssText) out.push(r.cssText);
15107
+ }
15108
+ }
15109
+ }
15110
+ } catch {
15111
+ }
15112
+ cachedFontFaceCss = out.join("\n");
15113
+ return cachedFontFaceCss;
15114
+ }
15025
15115
  async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
15026
15116
  try {
15027
15117
  const parser = new DOMParser();
@@ -15051,6 +15141,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
15051
15141
  stripSuspiciousFullPageOverlayNodes(svgToDraw);
15052
15142
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
15053
15143
  sanitizeSvgTreeForPdf(svgToDraw);
15144
+ await rasterizeShadowMarkers(svgToDraw);
15054
15145
  return svgToDraw;
15055
15146
  } catch {
15056
15147
  return null;