@pixldocs/canvas-renderer 0.5.99 → 0.5.102

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
@@ -3682,28 +3682,33 @@ function updateCoverLayout(g) {
3682
3682
  const hasCustomSvgMask = Boolean(
3683
3683
  currentClipPath && (isSvgMaskClipPath(currentClipPath) || isLuminanceMaskClipPath(currentClipPath) || currentClipPath.__svgMask)
3684
3684
  );
3685
- if (hasCustomSvgMask) {
3685
+ const hasEdgeFadeMask = Boolean(currentClipPath && currentClipPath.__edgeFadeMask);
3686
+ if (hasCustomSvgMask || hasEdgeFadeMask) {
3686
3687
  if (isSvgMaskClipPath(currentClipPath)) {
3687
3688
  syncSvgMaskClipPath(g);
3688
3689
  } else if (currentClipPath && typeof currentClipPath.set === "function") {
3689
- const baseW = Math.max(1, Number(currentClipPath.width) || frameW);
3690
- const baseH = Math.max(1, Number(currentClipPath.height) || frameH);
3691
- currentClipPath.set({
3692
- left: 0,
3693
- top: 0,
3694
- originX: "center",
3695
- originY: "center",
3696
- scaleX: frameW / baseW,
3697
- scaleY: frameH / baseH,
3698
- selectable: false,
3699
- evented: false,
3700
- hasControls: false,
3701
- hasBorders: false
3702
- });
3703
- currentClipPath.absolutePositioned = false;
3704
- currentClipPath.excludeFromExport = true;
3705
- currentClipPath.dirty = true;
3706
- currentClipPath.setCoords();
3690
+ if (hasEdgeFadeMask) {
3691
+ currentClipPath.dirty = true;
3692
+ } else {
3693
+ const baseW = Math.max(1, Number(currentClipPath.width) || frameW);
3694
+ const baseH = Math.max(1, Number(currentClipPath.height) || frameH);
3695
+ currentClipPath.set({
3696
+ left: 0,
3697
+ top: 0,
3698
+ originX: "center",
3699
+ originY: "center",
3700
+ scaleX: frameW / baseW,
3701
+ scaleY: frameH / baseH,
3702
+ selectable: false,
3703
+ evented: false,
3704
+ hasControls: false,
3705
+ hasBorders: false
3706
+ });
3707
+ currentClipPath.absolutePositioned = false;
3708
+ currentClipPath.excludeFromExport = true;
3709
+ currentClipPath.dirty = true;
3710
+ currentClipPath.setCoords();
3711
+ }
3707
3712
  }
3708
3713
  } else {
3709
3714
  const needsNewClipPath = !g.clipPath || shape === "circle" && !(g.clipPath instanceof fabric__namespace.Ellipse) || shape !== "circle" && !(g.clipPath instanceof fabric__namespace.Rect);
@@ -4660,10 +4665,12 @@ function exitCropMode(g, commit = true) {
4660
4665
  g.hasControls = true;
4661
4666
  g.borderDashArray = void 0;
4662
4667
  installCanvaMaskControls(g);
4663
- g.borderColor = void 0;
4664
- g.cornerColor = void 0;
4665
- g.cornerStrokeColor = void 0;
4668
+ g.borderColor = "#4f46e5";
4669
+ g.borderDashArray = void 0;
4670
+ g.cornerColor = "#ffffff";
4671
+ g.cornerStrokeColor = "#4f46e5";
4666
4672
  g.cornerStyle = "rect";
4673
+ g.transparentCorners = false;
4667
4674
  g[CROP_MODE_FLAG] = false;
4668
4675
  g.__cropZoomLastPointer = void 0;
4669
4676
  g.__lastPointerForCrop = void 0;
@@ -5085,10 +5092,14 @@ function applyTextBackground(obj, cfg) {
5085
5092
  const blur = Math.max(0, Number(shadow.blur ?? 0));
5086
5093
  const shadowColor = String(shadow.color);
5087
5094
  const pad = Math.max(16, Math.ceil(blur * 4) + Math.ceil(Math.max(Math.abs(ox), Math.abs(oy))) + 8);
5088
- const bx = -w / 2 - pL - pad;
5089
- const by = -h / 2 - pT - pad;
5090
- const bw = w + pL + pR + pad * 2;
5091
- const bh = h + pT + pB + pad * 2;
5095
+ const shadowBounds = unionBounds([
5096
+ ...rects,
5097
+ computeTextVisualBounds(this, w, h)
5098
+ ]);
5099
+ const bx = shadowBounds.x - pad;
5100
+ const by = shadowBounds.y - pad;
5101
+ const bw = shadowBounds.w + pad * 2;
5102
+ const bh = shadowBounds.h + pad * 2;
5092
5103
  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)}"`;
5093
5104
  const wrapShadow = (markup) => blur <= 0 ? `<g transform="translate(${ox.toFixed(3)} ${oy.toFixed(3)})">${markup}</g>` : `<g class="__pdShadowRaster" ${dataAttrs}>${markup}</g>`;
5094
5105
  if (hasBg && (bg == null ? void 0 : bg.shadowAffectsBg) !== false) {
@@ -5150,6 +5161,51 @@ function buildRoundedRectPathD(x, y, w, h, rTL, rTR, rBR, rBL) {
5150
5161
  parts.push("Z");
5151
5162
  return parts.join(" ");
5152
5163
  }
5164
+ function unionBounds(bounds) {
5165
+ const valid = bounds.filter((b) => Number.isFinite(b.x) && Number.isFinite(b.y) && b.w > 0 && b.h > 0);
5166
+ if (valid.length === 0) return { x: 0, y: 0, w: 1, h: 1 };
5167
+ const minX = Math.min(...valid.map((b) => b.x));
5168
+ const minY = Math.min(...valid.map((b) => b.y));
5169
+ const maxX = Math.max(...valid.map((b) => b.x + b.w));
5170
+ const maxY = Math.max(...valid.map((b) => b.y + b.h));
5171
+ return { x: minX, y: minY, w: Math.max(1, maxX - minX), h: Math.max(1, maxY - minY) };
5172
+ }
5173
+ function computeTextVisualBounds(obj, w, h) {
5174
+ var _a;
5175
+ const lines = (obj == null ? void 0 : obj._textLines) ?? [];
5176
+ if (!lines || lines.length === 0) return { x: -w / 2, y: -h / 2, w, h };
5177
+ const rects = [];
5178
+ const halfW = w / 2;
5179
+ const halfH = h / 2;
5180
+ const lineHeightRatio = Math.max(0.01, Number((obj == null ? void 0 : obj.lineHeight) ?? 1) || 1);
5181
+ let cursorY = -halfH;
5182
+ for (let i = 0; i < lines.length; i++) {
5183
+ let lineW = 0;
5184
+ let lineLeft = 0;
5185
+ let lineH = 0;
5186
+ try {
5187
+ lineW = obj.getLineWidth(i) || 0;
5188
+ } catch {
5189
+ lineW = 0;
5190
+ }
5191
+ try {
5192
+ lineLeft = ((_a = obj._getLineLeftOffset) == null ? void 0 : _a.call(obj, i)) ?? 0;
5193
+ } catch {
5194
+ lineLeft = 0;
5195
+ }
5196
+ try {
5197
+ lineH = obj.getHeightOfLine(i) || 0;
5198
+ } catch {
5199
+ lineH = 0;
5200
+ }
5201
+ const rawSlotH = i === lines.length - 1 ? lineH / lineHeightRatio : lineH;
5202
+ const usedH = cursorY + halfH;
5203
+ const slotH = Math.max(0, Math.min(rawSlotH, h - usedH));
5204
+ if (lineW > 0 && slotH > 0) rects.push({ x: -halfW + lineLeft, y: cursorY, w: lineW, h: slotH });
5205
+ cursorY += slotH;
5206
+ }
5207
+ return unionBounds(rects.length > 0 ? rects : [{ x: -w / 2, y: -h / 2, w, h }]);
5208
+ }
5153
5209
  function computeBgRects(obj, w, h, pT, pR, pB, pL, fit) {
5154
5210
  var _a;
5155
5211
  if (!fit) {
@@ -6144,6 +6200,127 @@ function renderSmartElementToDataUri(type, props, width, height) {
6144
6200
  if (!svg) return null;
6145
6201
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
6146
6202
  }
6203
+ function hasEdgeFade(p) {
6204
+ if (!p) return false;
6205
+ const sides = [
6206
+ [p.fadeTopSize, p.fadeTopAmount],
6207
+ [p.fadeRightSize, p.fadeRightAmount],
6208
+ [p.fadeBottomSize, p.fadeBottomAmount],
6209
+ [p.fadeLeftSize, p.fadeLeftAmount]
6210
+ ];
6211
+ for (const [size, amount] of sides) {
6212
+ const s = Number(size) || 0;
6213
+ const a = amount == null ? 1 : Number(amount);
6214
+ if (s > 0 && a < 1) return true;
6215
+ }
6216
+ return false;
6217
+ }
6218
+ function edgeFadeKey(p) {
6219
+ if (!hasEdgeFade(p)) return "";
6220
+ return [
6221
+ (p == null ? void 0 : p.fadeTopSize) ?? 0,
6222
+ (p == null ? void 0 : p.fadeTopAmount) ?? 1,
6223
+ (p == null ? void 0 : p.fadeRightSize) ?? 0,
6224
+ (p == null ? void 0 : p.fadeRightAmount) ?? 1,
6225
+ (p == null ? void 0 : p.fadeBottomSize) ?? 0,
6226
+ (p == null ? void 0 : p.fadeBottomAmount) ?? 1,
6227
+ (p == null ? void 0 : p.fadeLeftSize) ?? 0,
6228
+ (p == null ? void 0 : p.fadeLeftAmount) ?? 1,
6229
+ (p == null ? void 0 : p.fadeTopHardness) ?? 1,
6230
+ (p == null ? void 0 : p.fadeRightHardness) ?? 1,
6231
+ (p == null ? void 0 : p.fadeBottomHardness) ?? 1,
6232
+ (p == null ? void 0 : p.fadeLeftHardness) ?? 1
6233
+ ].join("|");
6234
+ }
6235
+ function clamp01(n) {
6236
+ if (!Number.isFinite(n)) return 0;
6237
+ return Math.max(0, Math.min(1, n));
6238
+ }
6239
+ function bakeEdgeFade(source, fade) {
6240
+ const w = source.naturalWidth || source.width || 0;
6241
+ const h = source.naturalHeight || source.height || 0;
6242
+ const canvas = document.createElement("canvas");
6243
+ canvas.width = Math.max(1, w);
6244
+ canvas.height = Math.max(1, h);
6245
+ const ctx = canvas.getContext("2d");
6246
+ if (!ctx) return canvas;
6247
+ ctx.drawImage(source, 0, 0, canvas.width, canvas.height);
6248
+ const clampHardness = (n) => {
6249
+ const v = Number(n);
6250
+ if (!Number.isFinite(v) || v <= 0) return 1;
6251
+ return Math.max(0.1, Math.min(5, v));
6252
+ };
6253
+ const sides = [
6254
+ { side: "top", size: clamp01(fade.fadeTopSize ?? 0), amount: clamp01(fade.fadeTopAmount ?? 1), hardness: clampHardness(fade.fadeTopHardness) },
6255
+ { side: "right", size: clamp01(fade.fadeRightSize ?? 0), amount: clamp01(fade.fadeRightAmount ?? 1), hardness: clampHardness(fade.fadeRightHardness) },
6256
+ { side: "bottom", size: clamp01(fade.fadeBottomSize ?? 0), amount: clamp01(fade.fadeBottomAmount ?? 1), hardness: clampHardness(fade.fadeBottomHardness) },
6257
+ { side: "left", size: clamp01(fade.fadeLeftSize ?? 0), amount: clamp01(fade.fadeLeftAmount ?? 1), hardness: clampHardness(fade.fadeLeftHardness) }
6258
+ ];
6259
+ for (const { side, size, amount, hardness } of sides) {
6260
+ if (size <= 0 || amount >= 1) continue;
6261
+ const mask = document.createElement("canvas");
6262
+ mask.width = canvas.width;
6263
+ mask.height = canvas.height;
6264
+ const mctx = mask.getContext("2d");
6265
+ if (!mctx) continue;
6266
+ mctx.fillStyle = "#000";
6267
+ mctx.fillRect(0, 0, mask.width, mask.height);
6268
+ let g;
6269
+ let x = 0, y = 0, rectW = mask.width, rectH = mask.height;
6270
+ const STOPS = 24;
6271
+ const innerAlpha = (t) => {
6272
+ const keepProgress = Math.pow(t, hardness);
6273
+ const erase = (1 - amount) * (1 - keepProgress);
6274
+ return 1 - erase;
6275
+ };
6276
+ const fillStops = (grad, reverse) => {
6277
+ for (let i = 0; i <= STOPS; i++) {
6278
+ const t = i / STOPS;
6279
+ const a = innerAlpha(t);
6280
+ grad.addColorStop(t, `rgba(0,0,0,${a})`);
6281
+ }
6282
+ };
6283
+ if (side === "top") {
6284
+ const band = Math.max(1, Math.round(mask.height * size));
6285
+ g = mctx.createLinearGradient(0, 0, 0, band);
6286
+ fillStops(g);
6287
+ rectH = band;
6288
+ } else if (side === "bottom") {
6289
+ const band = Math.max(1, Math.round(mask.height * size));
6290
+ y = mask.height - band;
6291
+ g = mctx.createLinearGradient(0, mask.height, 0, y);
6292
+ fillStops(g);
6293
+ rectH = band;
6294
+ } else if (side === "left") {
6295
+ const band = Math.max(1, Math.round(mask.width * size));
6296
+ g = mctx.createLinearGradient(0, 0, band, 0);
6297
+ fillStops(g);
6298
+ rectW = band;
6299
+ } else {
6300
+ const band = Math.max(1, Math.round(mask.width * size));
6301
+ x = mask.width - band;
6302
+ g = mctx.createLinearGradient(mask.width, 0, x, 0);
6303
+ fillStops(g);
6304
+ rectW = band;
6305
+ }
6306
+ mctx.fillStyle = g;
6307
+ mctx.fillRect(x, y, rectW, rectH);
6308
+ if (amount <= 1e-3) {
6309
+ const edgePx = Math.min(2, side === "top" || side === "bottom" ? rectH : rectW);
6310
+ mctx.fillStyle = "rgba(0,0,0,0)";
6311
+ mctx.globalCompositeOperation = "copy";
6312
+ if (side === "top") mctx.fillRect(0, 0, mask.width, edgePx);
6313
+ if (side === "bottom") mctx.fillRect(0, mask.height - edgePx, mask.width, edgePx);
6314
+ if (side === "left") mctx.fillRect(0, 0, edgePx, mask.height);
6315
+ if (side === "right") mctx.fillRect(mask.width - edgePx, 0, edgePx, mask.height);
6316
+ mctx.globalCompositeOperation = "source-over";
6317
+ }
6318
+ ctx.globalCompositeOperation = "destination-in";
6319
+ ctx.drawImage(mask, 0, 0);
6320
+ ctx.globalCompositeOperation = "source-over";
6321
+ }
6322
+ return canvas;
6323
+ }
6147
6324
  function angleToCoords(angleDeg) {
6148
6325
  const rad = angleDeg * Math.PI / 180;
6149
6326
  const x1 = 0.5 - Math.sin(rad) * 0.5;
@@ -7653,6 +7830,10 @@ const PageCanvas = react.forwardRef(
7653
7830
  fabricCanvas.on("mouse:dblclick", (e) => {
7654
7831
  if (!isActiveRef.current || !allowEditing) return;
7655
7832
  let target = e.target;
7833
+ if (!target) {
7834
+ const active = fabricCanvas.getActiveObject();
7835
+ if (active) target = active;
7836
+ }
7656
7837
  if (target && target instanceof fabric__namespace.Group && target.__cropGroup) {
7657
7838
  const ct = target.__cropData;
7658
7839
  if ((ct == null ? void 0 : ct._img) && !isCropGroupInCropMode(target)) {
@@ -7808,7 +7989,7 @@ const PageCanvas = react.forwardRef(
7808
7989
  visibilityUpdateInProgressRef.current = false;
7809
7990
  }
7810
7991
  doSyncRef.current = () => {
7811
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
7992
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
7812
7993
  const shouldSkipUpdates2 = syncLockedRef.current || editLockRef.current;
7813
7994
  const state = useEditorStore.getState();
7814
7995
  const elementsToSync = elements;
@@ -7969,15 +8150,22 @@ const PageCanvas = react.forwardRef(
7969
8150
  const storedImageUrl = existingObj.__imageSrc;
7970
8151
  const currentUrlNormalized = currentImageUrl.trim();
7971
8152
  const storedUrlNormalized = storedImageUrl ? String(storedImageUrl).trim() : "";
7972
- const svgColorMapStr = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
7973
- const storedColorMapStr = existingObj.__svgColorMap || "";
7974
- const colorMapChanged = svgColorMapStr !== storedColorMapStr;
7975
- const sourceUrlChanged = currentUrlNormalized !== storedUrlNormalized;
7976
- const needsReload = sourceUrlChanged || colorMapChanged;
7977
8153
  const hasUrl = currentUrlNormalized !== "";
7978
8154
  const isCropGroup2 = existingObj instanceof fabric__namespace.Group && existingObj.__cropGroup;
7979
8155
  const isPlaceholderGroup = isEmptyImagePlaceholderGroup(existingObj);
7980
8156
  const isPlaceholder = isPlaceholderGroup || !(existingObj instanceof fabric__namespace.FabricImage) || existingObj instanceof fabric__namespace.Group && !isCropGroup2;
8157
+ const svgColorMapStr = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
8158
+ const storedColorMapStr = existingObj.__svgColorMap || "";
8159
+ const colorMapChanged = svgColorMapStr !== storedColorMapStr;
8160
+ const sourceUrlChanged = currentUrlNormalized !== storedUrlNormalized;
8161
+ const newFadeKey = edgeFadeKey(element);
8162
+ const oldFadeKey = isCropGroup2 ? existingObj.__edgeFadeInputKey || "" : existingObj.__edgeFadeKey || "";
8163
+ const innerImg = (_e = existingObj == null ? void 0 : existingObj.__cropData) == null ? void 0 : _e._img;
8164
+ const innerOldKey = isCropGroup2 ? oldFadeKey : innerImg ? innerImg.__edgeFadeKey || "" : oldFadeKey;
8165
+ const cropFadeRendererMissing = isCropGroup2 && Boolean(newFadeKey) && !existingObj.__edgeFadeRenderConfig;
8166
+ const fadeKeyChanged = newFadeKey !== oldFadeKey || newFadeKey !== innerOldKey || cropFadeRendererMissing;
8167
+ const needsReload = sourceUrlChanged || colorMapChanged || fadeKeyChanged && !isCropGroup2;
8168
+ const needsCropGroupFadeUpdate = isCropGroup2 && fadeKeyChanged;
7981
8169
  const hadUrlBefore = storedImageUrl && String(storedImageUrl).trim() !== "";
7982
8170
  if (!hasUrl && hadUrlBefore) {
7983
8171
  const placeholder = isCropGroup2 ? createImagePlaceholderForGroup(element) : createImagePlaceholder(element);
@@ -7993,16 +8181,16 @@ const PageCanvas = react.forwardRef(
7993
8181
  fc.requestRenderAll();
7994
8182
  continue;
7995
8183
  }
7996
- const imageFitForReplace = element.imageFit || ((_e = element.style) == null ? void 0 : _e.imageFit) || "cover";
7997
- const clipShapeForReplace = element.clipShape ?? ((_f = element.style) == null ? void 0 : _f.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
8184
+ const imageFitForReplace = element.imageFit || ((_f = element.style) == null ? void 0 : _f.imageFit) || "cover";
8185
+ const clipShapeForReplace = element.clipShape ?? ((_g = element.style) == null ? void 0 : _g.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
7998
8186
  const needCropGroupForElement = imageFitForReplace !== "fill" || clipShapeForReplace && clipShapeForReplace !== "none";
7999
8187
  const plainImageNeedsCropGroup = hasUrl && !isCropGroup2 && existingObj instanceof fabric__namespace.FabricImage && needCropGroupForElement;
8000
- if (hasUrl && (needsReload || isPlaceholder || plainImageNeedsCropGroup)) {
8188
+ if (hasUrl && (needsReload || isPlaceholder || plainImageNeedsCropGroup || needsCropGroupFadeUpdate)) {
8001
8189
  if (needsReload && !isBeingTransformed && (!wasJustModified || sourceUrlChanged)) {
8002
8190
  loadImageAsync(element, existingObj, fc);
8003
8191
  } else if (plainImageNeedsCropGroup) {
8004
8192
  loadImageAsync(element, existingObj, fc);
8005
- } else if (!needsReload && isCropGroup2) {
8193
+ } else if ((!needsReload || needsCropGroupFadeUpdate) && isCropGroup2) {
8006
8194
  const ct = existingObj.__cropData;
8007
8195
  if (ct) {
8008
8196
  const resolvedCrop = pageTree.length > 0 ? getNodeBounds(element, pageTree) : { width: typeof element.width === "number" ? element.width : 200, height: typeof element.height === "number" ? element.height : 50 };
@@ -8099,6 +8287,7 @@ const PageCanvas = react.forwardRef(
8099
8287
  }
8100
8288
  }
8101
8289
  updateCoverLayout(existingObj);
8290
+ applyEdgeFadeFrameClipPath(existingObj, element, ct.frameW, ct.frameH, ct.shape || "rect", ct.rx || 0);
8102
8291
  if (allowEditing) {
8103
8292
  installCanvaMaskControls(existingObj);
8104
8293
  } else {
@@ -8408,7 +8597,7 @@ const PageCanvas = react.forwardRef(
8408
8597
  fc.add(placeholder);
8409
8598
  fc.bringObjectToFront(placeholder);
8410
8599
  const activeObj = fc.getActiveObject();
8411
- if (activeObj && (((_g = activeObj._ct) == null ? void 0 : _g.isCropGroup) || activeObj.__cropGroup)) {
8600
+ if (activeObj && (((_h = activeObj._ct) == null ? void 0 : _h.isCropGroup) || activeObj.__cropGroup)) {
8412
8601
  fc.setActiveObject(activeObj);
8413
8602
  }
8414
8603
  placeholder.dirty = true;
@@ -8448,7 +8637,7 @@ const PageCanvas = react.forwardRef(
8448
8637
  fc.add(obj);
8449
8638
  fc.bringObjectToFront(obj);
8450
8639
  const activeObj = fc.getActiveObject();
8451
- if (activeObj && (((_h = activeObj._ct) == null ? void 0 : _h.isCropGroup) || activeObj.__cropGroup)) {
8640
+ if (activeObj && (((_i = activeObj._ct) == null ? void 0 : _i.isCropGroup) || activeObj.__cropGroup)) {
8452
8641
  fc.setActiveObject(activeObj);
8453
8642
  }
8454
8643
  obj.dirty = true;
@@ -8482,7 +8671,7 @@ const PageCanvas = react.forwardRef(
8482
8671
  isRebuildingRef.current = false;
8483
8672
  fc.requestRenderAll();
8484
8673
  if (activeBeforeSync && fc.getObjects().includes(activeBeforeSync)) {
8485
- const isCropGroup2 = ((_i = activeBeforeSync._ct) == null ? void 0 : _i.isCropGroup) || activeBeforeSync.__cropGroup;
8674
+ const isCropGroup2 = ((_j = activeBeforeSync._ct) == null ? void 0 : _j.isCropGroup) || activeBeforeSync.__cropGroup;
8486
8675
  if (isCropGroup2) {
8487
8676
  fc.setActiveObject(activeBeforeSync);
8488
8677
  fc.requestRenderAll();
@@ -8904,6 +9093,7 @@ const PageCanvas = react.forwardRef(
8904
9093
  }
8905
9094
  }
8906
9095
  updateCoverLayout(obj);
9096
+ applyEdgeFadeFrameClipPath(obj, element, elementWidth, elementHeight, ct.shape || "rect", ct.rx || 0);
8907
9097
  obj.setCoords();
8908
9098
  obj.dirty = true;
8909
9099
  if (obj.clipPath) {
@@ -9487,8 +9677,131 @@ const PageCanvas = react.forwardRef(
9487
9677
  obj.__lastStrokeGradientJson = "null";
9488
9678
  }
9489
9679
  };
9680
+ const clamp2 = (v, min, max) => {
9681
+ return Math.max(min, Math.min(max, v));
9682
+ };
9683
+ const applyEdgeFadeFrameClipPath = (group, element, frameW, frameH, shape, rxRatio) => {
9684
+ var _a, _b, _c;
9685
+ const fadeElement = element;
9686
+ const fadeGroup = group;
9687
+ const inputKey = edgeFadeKey(fadeElement);
9688
+ const hasFade = hasEdgeFade(fadeElement);
9689
+ if (!fadeGroup.__edgeFadeOriginalDrawObject) {
9690
+ fadeGroup.__edgeFadeOriginalDrawObject = group.drawObject;
9691
+ }
9692
+ if (hasFade) {
9693
+ if ((_a = group.clipPath) == null ? void 0 : _a.__edgeFadeMask) {
9694
+ group.clipPath = void 0;
9695
+ updateCoverLayout(group);
9696
+ }
9697
+ fadeGroup.__edgeFadeRenderConfig = {
9698
+ key: `${inputKey}|${Math.round(frameW)}|${Math.round(frameH)}|${shape}|${rxRatio}`,
9699
+ frameW: Math.max(1, Number(frameW) || 1),
9700
+ frameH: Math.max(1, Number(frameH) || 1),
9701
+ topSize: clamp2(Number(fadeElement.fadeTopSize) || 0, 0, 1),
9702
+ topAmount: clamp2(fadeElement.fadeTopAmount == null ? 1 : Number(fadeElement.fadeTopAmount), 0, 1),
9703
+ rightSize: clamp2(Number(fadeElement.fadeRightSize) || 0, 0, 1),
9704
+ rightAmount: clamp2(fadeElement.fadeRightAmount == null ? 1 : Number(fadeElement.fadeRightAmount), 0, 1),
9705
+ bottomSize: clamp2(Number(fadeElement.fadeBottomSize) || 0, 0, 1),
9706
+ bottomAmount: clamp2(fadeElement.fadeBottomAmount == null ? 1 : Number(fadeElement.fadeBottomAmount), 0, 1),
9707
+ leftSize: clamp2(Number(fadeElement.fadeLeftSize) || 0, 0, 1),
9708
+ leftAmount: clamp2(fadeElement.fadeLeftAmount == null ? 1 : Number(fadeElement.fadeLeftAmount), 0, 1),
9709
+ topHardness: clamp2(Number(fadeElement.fadeTopHardness) || 1, 0.1, 5),
9710
+ rightHardness: clamp2(Number(fadeElement.fadeRightHardness) || 1, 0.1, 5),
9711
+ bottomHardness: clamp2(Number(fadeElement.fadeBottomHardness) || 1, 0.1, 5),
9712
+ leftHardness: clamp2(Number(fadeElement.fadeLeftHardness) || 1, 0.1, 5)
9713
+ };
9714
+ const originalDrawObject = fadeGroup.__edgeFadeOriginalDrawObject ?? group.drawObject;
9715
+ group.drawObject = function edgeFadeDrawObject(ctx, forClipping, context) {
9716
+ originalDrawObject.call(this, ctx, forClipping, context);
9717
+ if (forClipping) return;
9718
+ const cfg = this.__edgeFadeRenderConfig;
9719
+ if (!cfg) return;
9720
+ const liveW = Number(this.width) || 0;
9721
+ const liveH = Number(this.height) || 0;
9722
+ const w = liveW > 0 ? liveW : cfg.frameW;
9723
+ const h = liveH > 0 ? liveH : cfg.frameH;
9724
+ const x = -w / 2;
9725
+ const y = -h / 2;
9726
+ const paintBand = (side, size, amount, hardness) => {
9727
+ if (size <= 0 || amount >= 1) return;
9728
+ ctx.save();
9729
+ ctx.globalCompositeOperation = "destination-out";
9730
+ let gradient;
9731
+ const eraseAtEdge = 1 - amount;
9732
+ const STOPS = 24;
9733
+ const addStops = (g) => {
9734
+ for (let i = 0; i <= STOPS; i++) {
9735
+ const t = i / STOPS;
9736
+ const keepProgress = Math.pow(t, hardness);
9737
+ const a = eraseAtEdge * (1 - keepProgress);
9738
+ g.addColorStop(t, `rgba(0,0,0,${a})`);
9739
+ }
9740
+ };
9741
+ if (side === "top") {
9742
+ const band = Math.max(1, h * size);
9743
+ gradient = ctx.createLinearGradient(0, y, 0, y + band);
9744
+ addStops(gradient);
9745
+ ctx.fillStyle = gradient;
9746
+ ctx.fillRect(x, y, w, band);
9747
+ if (amount <= 1e-3) ctx.fillRect(x, y, w, Math.min(2, band));
9748
+ } else if (side === "bottom") {
9749
+ const band = Math.max(1, h * size);
9750
+ gradient = ctx.createLinearGradient(0, y + h, 0, y + h - band);
9751
+ addStops(gradient);
9752
+ ctx.fillStyle = gradient;
9753
+ ctx.fillRect(x, y + h - band, w, band);
9754
+ if (amount <= 1e-3) ctx.fillRect(x, y + h - Math.min(2, band), w, Math.min(2, band));
9755
+ } else if (side === "left") {
9756
+ const band = Math.max(1, w * size);
9757
+ gradient = ctx.createLinearGradient(x, 0, x + band, 0);
9758
+ addStops(gradient);
9759
+ ctx.fillStyle = gradient;
9760
+ ctx.fillRect(x, y, band, h);
9761
+ if (amount <= 1e-3) ctx.fillRect(x, y, Math.min(2, band), h);
9762
+ } else {
9763
+ const band = Math.max(1, w * size);
9764
+ gradient = ctx.createLinearGradient(x + w, 0, x + w - band, 0);
9765
+ addStops(gradient);
9766
+ ctx.fillStyle = gradient;
9767
+ ctx.fillRect(x + w - band, y, band, h);
9768
+ if (amount <= 1e-3) ctx.fillRect(x + w - Math.min(2, band), y, Math.min(2, band), h);
9769
+ }
9770
+ ctx.restore();
9771
+ };
9772
+ paintBand("top", cfg.topSize, cfg.topAmount, cfg.topHardness);
9773
+ paintBand("right", cfg.rightSize, cfg.rightAmount, cfg.rightHardness);
9774
+ paintBand("bottom", cfg.bottomSize, cfg.bottomAmount, cfg.bottomHardness);
9775
+ paintBand("left", cfg.leftSize, cfg.leftAmount, cfg.leftHardness);
9776
+ };
9777
+ group.set({ objectCaching: true, noScaleCache: false });
9778
+ fadeGroup.__edgeFadeKey = fadeGroup.__edgeFadeRenderConfig.key;
9779
+ fadeGroup.__edgeFadeInputKey = inputKey;
9780
+ } else {
9781
+ if (fadeGroup.__edgeFadeOriginalDrawObject) {
9782
+ group.drawObject = fadeGroup.__edgeFadeOriginalDrawObject;
9783
+ }
9784
+ delete fadeGroup.__edgeFadeRenderConfig;
9785
+ delete fadeGroup.__edgeFadeKey;
9786
+ delete fadeGroup.__edgeFadeInputKey;
9787
+ if ((_b = group.clipPath) == null ? void 0 : _b.__edgeFadeMask) {
9788
+ group.clipPath = void 0;
9789
+ updateCoverLayout(group);
9790
+ }
9791
+ }
9792
+ if (group.clipPath) {
9793
+ group.clipPath.dirty = true;
9794
+ group.clipPath.setCoords();
9795
+ }
9796
+ fadeGroup.dirty = true;
9797
+ const children = group.getObjects();
9798
+ children.forEach((child) => {
9799
+ child.dirty = true;
9800
+ });
9801
+ (_c = group.canvas) == null ? void 0 : _c.requestRenderAll();
9802
+ };
9490
9803
  const loadImageAsync = async (element, placeholder, fc) => {
9491
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
9804
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
9492
9805
  const imageUrl = element.src || element.imageUrl;
9493
9806
  if (!imageUrl) return;
9494
9807
  const elementId = element.id;
@@ -9501,7 +9814,9 @@ const PageCanvas = react.forwardRef(
9501
9814
  const existingImg = ct == null ? void 0 : ct._img;
9502
9815
  const existingSrc = placeholder.__imageSrc;
9503
9816
  const existingSvgColorMap = placeholder.__svgColorMap || "";
9504
- if (existingImg && existingSrc === imageUrl && existingSvgColorMap === nextSvgColorMap) {
9817
+ const existingFadeKey = existingImg && existingImg.__edgeFadeKey || placeholder.__edgeFadeKey || "";
9818
+ const nextFadeKey = edgeFadeKey(element);
9819
+ if (existingImg && existingSrc === imageUrl && existingSvgColorMap === nextSvgColorMap && existingFadeKey === nextFadeKey) {
9505
9820
  return placeholder;
9506
9821
  }
9507
9822
  }
@@ -9518,6 +9833,22 @@ const PageCanvas = react.forwardRef(
9518
9833
  if (!fabricRef.current || !isLatestRequest()) return;
9519
9834
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat);
9520
9835
  if (!isLatestRequest()) return;
9836
+ const imageFitForFade = element.imageFit || ((_a = element.style) == null ? void 0 : _a.imageFit) || "cover";
9837
+ const clipShapeForFade = element.clipShape ?? ((_b = element.style) == null ? void 0 : _b.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9838
+ const willUseCropGroupForFade = imageFitForFade !== "fill" || clipShapeForFade && clipShapeForFade !== "none";
9839
+ try {
9840
+ if (hasEdgeFade(element) && !willUseCropGroupForFade) {
9841
+ const srcEl = (_c = img.getElement) == null ? void 0 : _c.call(img);
9842
+ if (srcEl) {
9843
+ const baked = bakeEdgeFade(srcEl, element);
9844
+ img.setElement(baked);
9845
+ img.__edgeFadeKey = edgeFadeKey(element);
9846
+ img.dirty = true;
9847
+ }
9848
+ }
9849
+ } catch (e) {
9850
+ console.warn("[edgeFade] bake failed:", e);
9851
+ }
9521
9852
  const isHidden = !element.visible;
9522
9853
  img.set({
9523
9854
  originX: "left",
@@ -9550,8 +9881,8 @@ const PageCanvas = react.forwardRef(
9550
9881
  });
9551
9882
  img.setCoords();
9552
9883
  } else {
9553
- const imageFit = element.imageFit || ((_a = element.style) == null ? void 0 : _a.imageFit) || "cover";
9554
- const clipShape = element.clipShape ?? ((_b = element.style) == null ? void 0 : _b.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9884
+ const imageFit = element.imageFit || ((_d = element.style) == null ? void 0 : _d.imageFit) || "cover";
9885
+ const clipShape = element.clipShape ?? ((_e = element.style) == null ? void 0 : _e.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9555
9886
  const needCropGroup2 = imageFit !== "fill" || clipShape && clipShape !== "none";
9556
9887
  const imgNaturalWidth = img.width || 1;
9557
9888
  const imgNaturalHeight = img.height || 1;
@@ -9578,7 +9909,7 @@ const PageCanvas = react.forwardRef(
9578
9909
  if (imageFit === "fill" && !needCropGroup2) {
9579
9910
  const finalScaleX = baseScaleX * (element.scaleX ?? 1);
9580
9911
  const finalScaleY = baseScaleY * (element.scaleY ?? 1);
9581
- const pageTreeForCreate = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_c = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _c.children) ?? [];
9912
+ const pageTreeForCreate = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_f = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _f.children) ?? [];
9582
9913
  const createPos = pageTreeForCreate.length > 0 ? (() => {
9583
9914
  const node = findNodeById(pageTreeForCreate, element.id);
9584
9915
  return node ? getAbsoluteBounds(node, pageTreeForCreate) : { left: element.left ?? 0, top: element.top ?? 0 };
@@ -9623,12 +9954,12 @@ const PageCanvas = react.forwardRef(
9623
9954
  }
9624
9955
  img.__imageSrc = imageUrl;
9625
9956
  img.__svgColorMap = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
9626
- const imageFitFinal = element.imageFit || ((_d = element.style) == null ? void 0 : _d.imageFit) || "cover";
9627
- const clipShapeFinal = element.clipShape ?? ((_e = element.style) == null ? void 0 : _e.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9957
+ const imageFitFinal = element.imageFit || ((_g = element.style) == null ? void 0 : _g.imageFit) || "cover";
9958
+ const clipShapeFinal = element.clipShape ?? ((_h = element.style) == null ? void 0 : _h.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9628
9959
  const needCropGroup = imageFitFinal !== "fill" || clipShapeFinal && clipShapeFinal !== "none";
9629
9960
  let finalObject = img;
9630
9961
  if (needCropGroup) {
9631
- const pageTreeForCropResolve = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_f = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _f.children) ?? [];
9962
+ const pageTreeForCropResolve = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_i = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _i.children) ?? [];
9632
9963
  const nodeForSize = pageTreeForCropResolve.length ? findNodeById(pageTreeForCropResolve, element.id) : null;
9633
9964
  const w = nodeForSize && isElement(nodeForSize) ? nodeForSize.width : element.width;
9634
9965
  const h = nodeForSize && isElement(nodeForSize) ? nodeForSize.height : element.height;
@@ -9660,16 +9991,16 @@ const PageCanvas = react.forwardRef(
9660
9991
  let panY = element.cropPanY ?? 0.5;
9661
9992
  let zoom2 = element.cropZoom ?? 1;
9662
9993
  if (existingCropGroup) {
9663
- const existingImg = (_g = existingCropGroup.__cropData) == null ? void 0 : _g._img;
9994
+ const existingImg = (_j = existingCropGroup.__cropData) == null ? void 0 : _j._img;
9664
9995
  if (existingImg) {
9665
- panX = ((_h = existingImg._ct) == null ? void 0 : _h.panX) ?? existingImg.__panX ?? panX;
9666
- panY = ((_i = existingImg._ct) == null ? void 0 : _i.panY) ?? existingImg.__panY ?? panY;
9667
- zoom2 = ((_j = existingImg._ct) == null ? void 0 : _j.zoom) ?? zoom2;
9996
+ panX = ((_k = existingImg._ct) == null ? void 0 : _k.panX) ?? existingImg.__panX ?? panX;
9997
+ panY = ((_l = existingImg._ct) == null ? void 0 : _l.panY) ?? existingImg.__panY ?? panY;
9998
+ zoom2 = ((_m = existingImg._ct) == null ? void 0 : _m.zoom) ?? zoom2;
9668
9999
  }
9669
10000
  }
9670
10001
  const isDynamicField = dynamicFieldIds.includes(element.id);
9671
10002
  const canBeEvented = isEditorMode || isPreviewMode && isDynamicField;
9672
- const pageTreeForCrop = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_k = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _k.children) ?? [];
10003
+ const pageTreeForCrop = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_n = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _n.children) ?? [];
9673
10004
  const createPosForCrop = pageTreeForCrop.length > 0 ? (() => {
9674
10005
  const node = findNodeById(pageTreeForCrop, element.id);
9675
10006
  return node ? getAbsoluteBounds(node, pageTreeForCrop) : { left: element.left ?? 0, top: element.top ?? 0 };
@@ -9720,17 +10051,18 @@ const PageCanvas = react.forwardRef(
9720
10051
  } else {
9721
10052
  installCanvaMaskControls(cropGroup);
9722
10053
  }
9723
- const cropImg = (_l = cropGroup.__cropData) == null ? void 0 : _l._img;
10054
+ const cropImg = (_o = cropGroup.__cropData) == null ? void 0 : _o._img;
9724
10055
  if (cropImg) {
9725
10056
  cropImg._ct = { panX, panY, zoom: zoom2 };
9726
10057
  updateCoverLayout(cropGroup);
9727
10058
  }
10059
+ applyEdgeFadeFrameClipPath(cropGroup, element, frameW, frameH, shape, rxRatio);
9728
10060
  setObjectData(cropGroup, element.id);
9729
10061
  cropGroup.__imageElement = cropImg;
9730
10062
  cropGroup.__imageSrc = imageUrl;
9731
10063
  cropGroup.__svgColorMap = nextSvgColorMap;
9732
10064
  if (cropImg && element.imageNaturalWidth == null) {
9733
- const el = ((_m = cropImg.getElement) == null ? void 0 : _m.call(cropImg)) ?? cropImg._element;
10065
+ const el = ((_p = cropImg.getElement) == null ? void 0 : _p.call(cropImg)) ?? cropImg._element;
9734
10066
  const orig = typeof cropImg.getOriginalSize === "function" ? cropImg.getOriginalSize() : null;
9735
10067
  const nw = (orig == null ? void 0 : orig.width) ?? (el == null ? void 0 : el.naturalWidth) ?? cropImg.width;
9736
10068
  const nh = (orig == null ? void 0 : orig.height) ?? (el == null ? void 0 : el.naturalHeight) ?? cropImg.height;
@@ -13103,7 +13435,7 @@ function PixldocsPreview(props) {
13103
13435
  !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..." }) })
13104
13436
  ] });
13105
13437
  }
13106
- const PACKAGE_VERSION = "0.5.99";
13438
+ const PACKAGE_VERSION = "0.5.101";
13107
13439
  const roundParityValue = (value) => {
13108
13440
  if (typeof value !== "number") return value;
13109
13441
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -13891,12 +14223,69 @@ class PixldocsRenderer {
13891
14223
  { width: canvasWidth, height: canvasHeight },
13892
14224
  { cssOnly: false, backstoreOnly: false }
13893
14225
  );
14226
+ const fadeBakeRecords = [];
14227
+ try {
14228
+ const objs = fabricInstance.getObjects().slice();
14229
+ for (const obj of objs) {
14230
+ const isFadedCropGroup = obj instanceof fabric__namespace.Group && (Boolean(obj.__edgeFadeRenderConfig) || Boolean(obj.__edgeFadeKey) || Boolean(obj.__edgeFadeInputKey));
14231
+ if (!isFadedCropGroup) continue;
14232
+ try {
14233
+ const baked = obj.toCanvasElement({
14234
+ multiplier: 2,
14235
+ enableRetinaScaling: false
14236
+ });
14237
+ const rect = obj.getBoundingRect();
14238
+ const replacement = new fabric__namespace.FabricImage(baked, {
14239
+ left: rect.left,
14240
+ top: rect.top,
14241
+ originX: "left",
14242
+ originY: "top",
14243
+ scaleX: rect.width / baked.width,
14244
+ scaleY: rect.height / baked.height,
14245
+ selectable: false,
14246
+ evented: false,
14247
+ objectCaching: false
14248
+ });
14249
+ const insertIndex = fabricInstance._objects.indexOf(obj);
14250
+ const prevExclude = obj.excludeFromExport;
14251
+ obj.excludeFromExport = true;
14252
+ if (insertIndex >= 0) {
14253
+ fabricInstance.insertAt(insertIndex + 1, replacement);
14254
+ } else {
14255
+ fabricInstance.add(replacement);
14256
+ }
14257
+ fadeBakeRecords.push({
14258
+ original: obj,
14259
+ replacement,
14260
+ prevExclude,
14261
+ insertIndex
14262
+ });
14263
+ } catch (bakeErr) {
14264
+ console.warn("[canvas-renderer][edgeFade] bake-for-svg failed:", bakeErr);
14265
+ }
14266
+ }
14267
+ if (fadeBakeRecords.length) {
14268
+ fabricInstance.renderAll();
14269
+ console.log(
14270
+ `[canvas-renderer][edgeFade] baked ${fadeBakeRecords.length} faded object(s) for SVG capture`
14271
+ );
14272
+ }
14273
+ } catch (e) {
14274
+ console.warn("[canvas-renderer][edgeFade] bake pass failed:", e);
14275
+ }
13894
14276
  const rawSvgString = fabricInstance.toSVG();
13895
14277
  const svgString = this.normalizeSvgDimensions(
13896
14278
  rawSvgString,
13897
14279
  canvasWidth,
13898
14280
  canvasHeight
13899
14281
  );
14282
+ for (const rec of fadeBakeRecords) {
14283
+ try {
14284
+ fabricInstance.remove(rec.replacement);
14285
+ rec.original.excludeFromExport = rec.prevExclude;
14286
+ } catch {
14287
+ }
14288
+ }
13900
14289
  fabricInstance.enableRetinaScaling = prevRetina;
13901
14290
  fabricInstance.setDimensions(
13902
14291
  { width: prevWidth, height: prevHeight },
@@ -15233,6 +15622,33 @@ const GRADIENT_ATTRS_LINEAR = ["x1", "y1", "x2", "y2", "gradientUnits", "gradien
15233
15622
  const GRADIENT_ATTRS_RADIAL = ["cx", "cy", "r", "fx", "fy", "gradientUnits", "gradientTransform", "spreadMethod"];
15234
15623
  const URL_GRADIENT_RE = /^\s*url\s*\(\s*(['"]?)([^)]+?)\1\s*\)/i;
15235
15624
  const SHADOW_RASTER_ALPHA_COMPENSATION = 0.84;
15625
+ function collectFontSpecsFromMarkup(markup) {
15626
+ const specs = /* @__PURE__ */ new Set();
15627
+ const re = /<(?:text|tspan)\b[^>]*>/gi;
15628
+ let match;
15629
+ while ((match = re.exec(markup)) !== null) {
15630
+ const tag = match[0];
15631
+ const get = (attr) => {
15632
+ const m = tag.match(new RegExp(`\\s${attr}\\s*=\\s*"([^"]*)"`, "i"));
15633
+ if (m) return m[1];
15634
+ const styleM = tag.match(/\sstyle\s*=\s*"([^"]*)"/i);
15635
+ if (styleM) {
15636
+ const sm = styleM[1].match(new RegExp(`${attr}\\s*:\\s*([^;]+)`, "i"));
15637
+ if (sm) return sm[1].trim();
15638
+ }
15639
+ return null;
15640
+ };
15641
+ const family = (get("font-family") || "").split(",")[0].replace(/['"]/g, "").trim();
15642
+ if (!family) continue;
15643
+ const weight = get("font-weight") || "400";
15644
+ const style = get("font-style") || "normal";
15645
+ const size = get("font-size") || "16px";
15646
+ const sizePx = /[a-z%]/i.test(size) ? size : `${size}px`;
15647
+ const famSpec = /\s/.test(family) ? `"${family}"` : family;
15648
+ specs.add(`${style} ${weight} ${sizePx} ${famSpec}`);
15649
+ }
15650
+ return Array.from(specs);
15651
+ }
15236
15652
  function parseColor(color) {
15237
15653
  if (!color) return null;
15238
15654
  const raw = color.trim().toLowerCase();
@@ -16268,7 +16684,7 @@ async function convertSvgTextDecorationsToLinesString(svgStr) {
16268
16684
  }
16269
16685
  }
16270
16686
  async function rasterizeShadowMarkers(svg) {
16271
- var _a, _b, _c, _d, _e;
16687
+ var _a, _b, _c, _d, _e, _f;
16272
16688
  if (typeof window === "undefined" || typeof document === "undefined") return;
16273
16689
  const markers = Array.from(svg.querySelectorAll("g.__pdShadowRaster"));
16274
16690
  if (markers.length === 0) return;
@@ -16293,6 +16709,17 @@ async function rasterizeShadowMarkers(svg) {
16293
16709
  continue;
16294
16710
  }
16295
16711
  const innerXml = Array.from(marker.childNodes).map((n) => n instanceof Element ? new XMLSerializer().serializeToString(n) : "").join("");
16712
+ try {
16713
+ const fontSpecs = collectFontSpecsFromMarkup(innerXml);
16714
+ if (fontSpecs.length > 0 && ((_c = document.fonts) == null ? void 0 : _c.load)) {
16715
+ await Promise.all(
16716
+ fontSpecs.map(
16717
+ (spec) => document.fonts.load(spec).catch(() => void 0)
16718
+ )
16719
+ );
16720
+ }
16721
+ } catch {
16722
+ }
16296
16723
  const scale = 2;
16297
16724
  const pxW = Math.min(4096, Math.max(8, Math.ceil(bw * scale)));
16298
16725
  const pxH = Math.min(4096, Math.max(8, Math.ceil(bh * scale)));
@@ -16302,7 +16729,7 @@ async function rasterizeShadowMarkers(svg) {
16302
16729
  const miniSvg = `<svg xmlns="${SVG_NS}" xmlns:xlink="${XLINK_NS}" width="${pxW}" height="${pxH}" viewBox="${bx} ${by} ${bw} ${bh}">${styleBlock}<defs><filter id="${filterId}" filterUnits="userSpaceOnUse" x="${bx}" y="${by}" width="${bw}" height="${bh}" color-interpolation-filters="sRGB"><feOffset dx="${ox}" dy="${oy}" result="offsetShadow" /><feGaussianBlur in="offsetShadow" stdDeviation="${stdDev}" /></filter></defs><g filter="url(#${filterId})">${innerXml}</g></svg>`;
16303
16730
  const dataUrl = await rasterSvgToPngDataUrl(miniSvg, pxW, pxH);
16304
16731
  if (!dataUrl) {
16305
- (_c = marker.parentNode) == null ? void 0 : _c.removeChild(marker);
16732
+ (_d = marker.parentNode) == null ? void 0 : _d.removeChild(marker);
16306
16733
  continue;
16307
16734
  }
16308
16735
  const img = svg.ownerDocument.createElementNS(SVG_NS, "image");
@@ -16314,11 +16741,11 @@ async function rasterizeShadowMarkers(svg) {
16314
16741
  img.setAttribute("preserveAspectRatio", "none");
16315
16742
  img.setAttributeNS(XLINK_NS, "xlink:href", dataUrl);
16316
16743
  img.setAttribute("href", dataUrl);
16317
- (_d = marker.parentNode) == null ? void 0 : _d.replaceChild(img, marker);
16744
+ (_e = marker.parentNode) == null ? void 0 : _e.replaceChild(img, marker);
16318
16745
  } catch (e) {
16319
16746
  console.warn("[pdf-export] rasterizeShadowMarkers failed for one marker:", e);
16320
16747
  try {
16321
- (_e = marker.parentNode) == null ? void 0 : _e.removeChild(marker);
16748
+ (_f = marker.parentNode) == null ? void 0 : _f.removeChild(marker);
16322
16749
  } catch {
16323
16750
  }
16324
16751
  }