@pixldocs/canvas-renderer 0.5.82 → 0.5.83

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.js CHANGED
@@ -475,29 +475,39 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
475
475
  const mode = group.layoutMode ?? "absolute";
476
476
  if (!isStackLayoutMode(mode)) return /* @__PURE__ */ new Map();
477
477
  const gap = group.stackSpacing ?? 8;
478
+ const padTop = group.paddingTop ?? 0;
479
+ const padLeft = group.paddingLeft ?? 0;
478
480
  const kids = group.children ?? [];
479
481
  const out = /* @__PURE__ */ new Map();
480
482
  if (isVerticalStackLayoutMode(mode)) {
481
- let prevBottom = 0;
483
+ let prevBottom = padTop;
484
+ let firstSeen = false;
482
485
  for (let i = 0; i < kids.length; i++) {
483
486
  const child = kids[i];
484
487
  const storedTop = getNodeTop(child);
485
488
  const storedLeft = getNodeLeft(child);
486
- const effectiveTop = i === 0 ? storedTop : prevBottom + gap + storedTop;
487
- out.set(child.id, { top: effectiveTop, left: storedLeft });
489
+ const mTop = child.marginTop ?? 0;
490
+ const mLeft = child.marginLeft ?? 0;
491
+ const effectiveTop = !firstSeen ? padTop + storedTop + mTop : prevBottom + gap + storedTop + mTop;
492
+ firstSeen = true;
493
+ out.set(child.id, { top: effectiveTop, left: padLeft + storedLeft + mLeft });
488
494
  const h = getNodeBounds(child, pageChildren).height;
489
- prevBottom = effectiveTop + h;
495
+ prevBottom = effectiveTop + h + (child.marginBottom ?? 0);
490
496
  }
491
497
  } else {
492
- let prevRight = 0;
498
+ let prevRight = padLeft;
499
+ let firstSeen = false;
493
500
  for (let i = 0; i < kids.length; i++) {
494
501
  const child = kids[i];
495
502
  const storedLeft = getNodeLeft(child);
496
503
  const storedTop = getNodeTop(child);
497
- const effectiveLeft = i === 0 ? storedLeft : prevRight + gap + storedLeft;
498
- out.set(child.id, { top: storedTop, left: effectiveLeft });
504
+ const mTop = child.marginTop ?? 0;
505
+ const mLeft = child.marginLeft ?? 0;
506
+ const effectiveLeft = !firstSeen ? padLeft + storedLeft + mLeft : prevRight + gap + storedLeft + mLeft;
507
+ firstSeen = true;
508
+ out.set(child.id, { top: padTop + storedTop + mTop, left: effectiveLeft });
499
509
  const w = getNodeBounds(child, pageChildren).width;
500
- prevRight = effectiveLeft + w;
510
+ prevRight = effectiveLeft + w + (child.marginRight ?? 0);
501
511
  }
502
512
  }
503
513
  return out;
@@ -522,6 +532,14 @@ function groupBoundsFromChildren(group, pageChildren, options) {
522
532
  maxX = Math.max(maxX, cl + b.width);
523
533
  maxY = Math.max(maxY, ct + b.height);
524
534
  }
535
+ if (isStack) {
536
+ const padRight = group.paddingRight ?? 0;
537
+ const padBottom = group.paddingBottom ?? 0;
538
+ return {
539
+ width: Math.max(1, maxX + padRight),
540
+ height: Math.max(1, maxY + padBottom)
541
+ };
542
+ }
525
543
  return {
526
544
  width: Math.max(1, maxX - minX),
527
545
  height: Math.max(1, maxY - minY)
@@ -584,7 +602,7 @@ function absoluteToStorePosition(absoluteLeft, absoluteTop, nodeId, pageChildren
584
602
  const prev = kids[idx - 1];
585
603
  const prevResolved = resolved.get(prev.id);
586
604
  const prevHeight = getNodeBounds(prev, pageChildren).height;
587
- const prevBottom = prevResolved ? prevResolved.top + prevHeight : 0;
605
+ const prevBottom = prevResolved ? prevResolved.top + prevHeight + (prev.marginBottom ?? 0) : 0;
588
606
  const storeTop = inParentTop - prevBottom - gap;
589
607
  return { left: storeLeft, top: Math.max(0, storeTop) };
590
608
  }
@@ -606,7 +624,7 @@ function reflowStackGroup(group, pageChildren, spacing) {
606
624
  const b = getNodeBounds(child, pageChildren);
607
625
  const currentTop = getNodeTop(child);
608
626
  const effectiveTop = i === 0 ? currentTop : prevBottom + gap;
609
- prevBottom = effectiveTop + b.height;
627
+ prevBottom = effectiveTop + b.height + (child.marginBottom ?? 0);
610
628
  if (i > 0) updates.set(child.id, { top: 0, left: firstLeft });
611
629
  }
612
630
  } else {
@@ -617,7 +635,7 @@ function reflowStackGroup(group, pageChildren, spacing) {
617
635
  const b = getNodeBounds(child, pageChildren);
618
636
  const currentLeft = getNodeLeft(child);
619
637
  const effectiveLeft = i === 0 ? currentLeft : prevRight + gap;
620
- prevRight = effectiveLeft + b.width;
638
+ prevRight = effectiveLeft + b.width + (child.marginRight ?? 0);
621
639
  if (i > 0) updates.set(child.id, { left: 0, top: firstTop });
622
640
  }
623
641
  }
@@ -769,7 +787,8 @@ const cloneCanvas = (canvas) => ({
769
787
  themeConfig: canvas.themeConfig ? {
770
788
  properties: canvas.themeConfig.properties.map((p) => ({ ...p })),
771
789
  variants: canvas.themeConfig.variants.map((v) => ({ ...v, values: { ...v.values } }))
772
- } : void 0
790
+ } : void 0,
791
+ pdfTextMode: canvas.pdfTextMode
773
792
  });
774
793
  const sameIdSet = (a, b) => {
775
794
  if (a.length !== b.length) return false;
@@ -1249,6 +1268,12 @@ const useEditorStore = create((set, get) => ({
1249
1268
  const committed = commitFromState(state, nextCanvas);
1250
1269
  return { canvas: nextCanvas, ...committed };
1251
1270
  }),
1271
+ setPdfTextMode: (mode) => set((state) => {
1272
+ if (state.canvas.pdfTextMode === mode) return {};
1273
+ const nextCanvas = { ...state.canvas, pdfTextMode: mode };
1274
+ const committed = commitFromState(state, nextCanvas);
1275
+ return { canvas: nextCanvas, ...committed };
1276
+ }),
1252
1277
  setZoom: (zoom) => set((state) => ({
1253
1278
  canvas: { ...state.canvas, zoom: Math.max(0.1, Math.min(10, zoom)) }
1254
1279
  })),
@@ -2436,7 +2461,8 @@ const useEditorStore = create((set, get) => ({
2436
2461
  formBindingMode: config.formBindingMode,
2437
2462
  boundFormDefId: config.boundFormDefId,
2438
2463
  boundFormDefName: config.boundFormDefName,
2439
- themeConfig: config.themeConfig ?? void 0
2464
+ themeConfig: config.themeConfig ?? void 0,
2465
+ pdfTextMode: config.pdfTextMode ?? "selectable"
2440
2466
  };
2441
2467
  const committed = commitFromState(state, nextCanvas);
2442
2468
  const out = {
@@ -4903,7 +4929,9 @@ function extractTextBgConfig(element) {
4903
4929
  if (!Number.isFinite(n)) return void 0;
4904
4930
  return Math.max(0, Math.min(1, n));
4905
4931
  })(),
4906
- shadowAffectsBg: element.textShadowAffectsBg !== false
4932
+ shadowAffectsBg: element.textShadowAffectsBg !== false,
4933
+ shadowAffectsText: element.textShadowAffectsText !== false,
4934
+ fitToText: element.textBgFitToText === true
4907
4935
  };
4908
4936
  }
4909
4937
  function hasTextBackground(cfg) {
@@ -4959,10 +4987,6 @@ function applyTextBackground(obj, cfg) {
4959
4987
  const pR = Math.max(0, Number(bg.padRight ?? 0));
4960
4988
  const pB = Math.max(0, Number(bg.padBottom ?? 0));
4961
4989
  const pL = Math.max(0, Number(bg.padLeft ?? 0));
4962
- const x = -w / 2 - pL;
4963
- const y = -h / 2 - pT;
4964
- const bgW = w + pL + pR;
4965
- const bgH = h + pT + pB;
4966
4990
  ctx.save();
4967
4991
  const suppressShadowOnBg = bg.shadowAffectsBg === false;
4968
4992
  if (suppressShadowOnBg) {
@@ -4971,24 +4995,38 @@ function applyTextBackground(obj, cfg) {
4971
4995
  ctx.shadowOffsetX = 0;
4972
4996
  ctx.shadowOffsetY = 0;
4973
4997
  }
4974
- buildRoundedRectPath2D(
4975
- ctx,
4976
- x,
4977
- y,
4978
- bgW,
4979
- bgH,
4980
- bg.rxTL ?? 0,
4981
- bg.rxTR ?? 0,
4982
- bg.rxBR ?? 0,
4983
- bg.rxBL ?? 0
4984
- );
4985
4998
  const op = typeof bg.opacity === "number" ? Math.max(0, Math.min(1, bg.opacity)) : 1;
4986
4999
  if (op < 1) ctx.globalAlpha = (ctx.globalAlpha ?? 1) * op;
4987
5000
  ctx.fillStyle = bg.color;
4988
- ctx.fill();
5001
+ const rects = computeBgRects(this, w, h, pT, pR, pB, pL, !!bg.fitToText);
5002
+ for (const r of rects) {
5003
+ buildRoundedRectPath2D(
5004
+ ctx,
5005
+ r.x,
5006
+ r.y,
5007
+ r.w,
5008
+ r.h,
5009
+ bg.rxTL ?? 0,
5010
+ bg.rxTR ?? 0,
5011
+ bg.rxBR ?? 0,
5012
+ bg.rxBL ?? 0
5013
+ );
5014
+ ctx.fill();
5015
+ }
4989
5016
  ctx.restore();
4990
5017
  }
4991
- originalRender(ctx);
5018
+ const suppressShadowOnText = bg && bg.shadowAffectsText === false;
5019
+ if (suppressShadowOnText) {
5020
+ ctx.save();
5021
+ ctx.shadowColor = "transparent";
5022
+ ctx.shadowBlur = 0;
5023
+ ctx.shadowOffsetX = 0;
5024
+ ctx.shadowOffsetY = 0;
5025
+ originalRender(ctx);
5026
+ ctx.restore();
5027
+ } else {
5028
+ originalRender(ctx);
5029
+ }
4992
5030
  };
4993
5031
  const originalToObject = obj.toObject.bind(obj);
4994
5032
  obj.toObject = function(propertiesToInclude) {
@@ -5014,20 +5052,18 @@ function applyTextBackground(obj, cfg) {
5014
5052
  const pR = Math.max(0, Number((bg == null ? void 0 : bg.padRight) ?? 0));
5015
5053
  const pB = Math.max(0, Number((bg == null ? void 0 : bg.padBottom) ?? 0));
5016
5054
  const pL = Math.max(0, Number((bg == null ? void 0 : bg.padLeft) ?? 0));
5017
- const x = -w / 2 - pL;
5018
- const y = -h / 2 - pT;
5019
- const bgW = w + pL + pR;
5020
- const bgH = h + pT + pB;
5021
- const bgD = buildRoundedRectPathD(
5022
- x,
5023
- y,
5024
- bgW,
5025
- bgH,
5055
+ const fit = !!(bg == null ? void 0 : bg.fitToText);
5056
+ const rects = computeBgRects(this, w, h, pT, pR, pB, pL, fit);
5057
+ const bgD = rects.map((r) => buildRoundedRectPathD(
5058
+ r.x,
5059
+ r.y,
5060
+ r.w,
5061
+ r.h,
5026
5062
  (bg == null ? void 0 : bg.rxTL) ?? 0,
5027
5063
  (bg == null ? void 0 : bg.rxTR) ?? 0,
5028
5064
  (bg == null ? void 0 : bg.rxBR) ?? 0,
5029
5065
  (bg == null ? void 0 : bg.rxBL) ?? 0
5030
- );
5066
+ )).join(" ");
5031
5067
  const bgFill = (bg == null ? void 0 : bg.color) || "";
5032
5068
  const bgOpacity = typeof (bg == null ? void 0 : bg.opacity) === "number" ? Math.max(0, Math.min(1, bg.opacity)) : 1;
5033
5069
  const bgOpacityAttr = bgOpacity < 1 ? ` fill-opacity="${bgOpacity}"` : "";
@@ -5052,9 +5088,11 @@ function applyTextBackground(obj, cfg) {
5052
5088
  const shadowBgPath = `<path d="${bgD}" fill="${escapeXmlAttr(shadowColor)}" />`;
5053
5089
  bgShadowMarker = wrapShadow(shadowBgPath);
5054
5090
  }
5055
- const inner = extractGInnerMarkup(svg);
5056
- const recoloredText = recolorSvgFills(inner, shadowColor);
5057
- if (recoloredText) textShadowMarker = wrapShadow(recoloredText);
5091
+ if ((bg == null ? void 0 : bg.shadowAffectsText) !== false) {
5092
+ const inner = extractGInnerMarkup(svg);
5093
+ const recoloredText = recolorSvgFills(inner, shadowColor);
5094
+ if (recoloredText) textShadowMarker = wrapShadow(recoloredText);
5095
+ }
5058
5096
  }
5059
5097
  const openTagMatch = svg.match(/^\s*<g\b[^>]*>/);
5060
5098
  const inserted = bgShadowMarker + bgPath + textShadowMarker;
@@ -5105,6 +5143,74 @@ function buildRoundedRectPathD(x, y, w, h, rTL, rTR, rBR, rBL) {
5105
5143
  parts.push("Z");
5106
5144
  return parts.join(" ");
5107
5145
  }
5146
+ function computeBgRects(obj, w, h, pT, pR, pB, pL, fit) {
5147
+ var _a;
5148
+ if (!fit) {
5149
+ return [{
5150
+ x: -w / 2 - pL,
5151
+ y: -h / 2 - pT,
5152
+ w: w + pL + pR,
5153
+ h: h + pT + pB
5154
+ }];
5155
+ }
5156
+ const lines = (obj == null ? void 0 : obj._textLines) ?? [];
5157
+ if (!lines || lines.length === 0) {
5158
+ return [{
5159
+ x: -w / 2 - pL,
5160
+ y: -h / 2 - pT,
5161
+ w: w + pL + pR,
5162
+ h: h + pT + pB
5163
+ }];
5164
+ }
5165
+ const rects = [];
5166
+ const halfW = w / 2;
5167
+ const halfH = h / 2;
5168
+ const lineHeightRatio = Math.max(0.01, Number((obj == null ? void 0 : obj.lineHeight) ?? 1) || 1);
5169
+ let cursorY = -halfH;
5170
+ for (let i = 0; i < lines.length; i++) {
5171
+ let lineW = 0;
5172
+ let lineLeft = 0;
5173
+ let lineH = 0;
5174
+ try {
5175
+ lineW = obj.getLineWidth(i) || 0;
5176
+ } catch {
5177
+ lineW = 0;
5178
+ }
5179
+ try {
5180
+ lineLeft = ((_a = obj._getLineLeftOffset) == null ? void 0 : _a.call(obj, i)) ?? 0;
5181
+ } catch {
5182
+ lineLeft = 0;
5183
+ }
5184
+ try {
5185
+ lineH = obj.getHeightOfLine(i) || 0;
5186
+ } catch {
5187
+ lineH = 0;
5188
+ }
5189
+ const rawSlotH = i === lines.length - 1 ? lineH / lineHeightRatio : lineH;
5190
+ const usedH = cursorY + halfH;
5191
+ const slotH = Math.max(0, Math.min(rawSlotH, h - usedH));
5192
+ if (lineW <= 0 || slotH <= 0) {
5193
+ cursorY += slotH;
5194
+ continue;
5195
+ }
5196
+ rects.push({
5197
+ x: -halfW + lineLeft - pL,
5198
+ y: cursorY - pT,
5199
+ w: lineW + pL + pR,
5200
+ h: slotH + pT + pB
5201
+ });
5202
+ cursorY += slotH;
5203
+ }
5204
+ if (rects.length === 0) {
5205
+ return [{
5206
+ x: -w / 2 - pL,
5207
+ y: -h / 2 - pT,
5208
+ w: w + pL + pR,
5209
+ h: h + pT + pB
5210
+ }];
5211
+ }
5212
+ return rects;
5213
+ }
5108
5214
  function escapeXmlAttr(s) {
5109
5215
  return String(s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5110
5216
  }
@@ -8124,10 +8230,14 @@ const PageCanvas = forwardRef(
8124
8230
  tr: element.textBgRxTR ?? 0,
8125
8231
  br: element.textBgRxBR ?? 0,
8126
8232
  bl: element.textBgRxBL ?? 0,
8233
+ ft: element.textBgFitToText === true,
8234
+ bo: element.textBgOpacity ?? 1,
8127
8235
  sc: element.textShadowColor ?? null,
8128
8236
  sb: element.textShadowBlur ?? 0,
8129
8237
  sx: element.textShadowOffsetX ?? 0,
8130
- sy: element.textShadowOffsetY ?? 0
8238
+ sy: element.textShadowOffsetY ?? 0,
8239
+ st: element.textShadowAffectsText !== false,
8240
+ sa: element.textShadowAffectsBg !== false
8131
8241
  }) !== (existingObj.__lastTextBgShadowJson ?? "") || // CRITICAL: Detect gradient fill/stroke changes — serialise to JSON for deep comparison
8132
8242
  JSON.stringify(element.fillGradient || null) !== (existingObj.__lastFillGradientJson ?? "null") || JSON.stringify(element.strokeGradient || null) !== (existingObj.__lastStrokeGradientJson ?? "null");
8133
8243
  const forceApplyFromPanel = syncTriggeredByPanelRef.current;
@@ -9041,10 +9151,14 @@ const PageCanvas = forwardRef(
9041
9151
  tr: element.textBgRxTR ?? 0,
9042
9152
  br: element.textBgRxBR ?? 0,
9043
9153
  bl: element.textBgRxBL ?? 0,
9154
+ ft: element.textBgFitToText === true,
9155
+ bo: element.textBgOpacity ?? 1,
9044
9156
  sc: element.textShadowColor ?? null,
9045
9157
  sb: element.textShadowBlur ?? 0,
9046
9158
  sx: element.textShadowOffsetX ?? 0,
9047
- sy: element.textShadowOffsetY ?? 0
9159
+ sy: element.textShadowOffsetY ?? 0,
9160
+ st: element.textShadowAffectsText !== false,
9161
+ sa: element.textShadowAffectsBg !== false
9048
9162
  });
9049
9163
  obj.dirty = true;
9050
9164
  } catch (err) {
@@ -14096,6 +14210,7 @@ async function fetchTTFAsBase64(url) {
14096
14210
  if (!res.ok) return null;
14097
14211
  const buf = await res.arrayBuffer();
14098
14212
  const bytes = new Uint8Array(buf);
14213
+ if (!isJsPdfEmbeddableTrueType(bytes)) return null;
14099
14214
  let binary = "";
14100
14215
  for (let i = 0; i < bytes.length; i++) {
14101
14216
  binary += String.fromCharCode(bytes[i]);
@@ -14107,6 +14222,33 @@ async function fetchTTFAsBase64(url) {
14107
14222
  return null;
14108
14223
  }
14109
14224
  }
14225
+ function isJsPdfEmbeddableTrueType(bytes) {
14226
+ if (bytes.length < 12) return false;
14227
+ const signature = String.fromCharCode(bytes[0], bytes[1], bytes[2], bytes[3]);
14228
+ if (signature !== "\0\0\0" && signature !== "true") return false;
14229
+ const u16 = (offset) => bytes[offset] << 8 | bytes[offset + 1];
14230
+ const u32 = (offset) => (bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
14231
+ const tableCount = u16(4);
14232
+ for (let i = 0; i < tableCount; i++) {
14233
+ const recordOffset = 12 + i * 16;
14234
+ if (recordOffset + 16 > bytes.length) return false;
14235
+ const tag = String.fromCharCode(bytes[recordOffset], bytes[recordOffset + 1], bytes[recordOffset + 2], bytes[recordOffset + 3]);
14236
+ if (tag !== "cmap") continue;
14237
+ const cmapOffset = u32(recordOffset + 8);
14238
+ const cmapLength = u32(recordOffset + 12);
14239
+ if (cmapOffset + Math.min(cmapLength, 4) > bytes.length) return false;
14240
+ const subtables = u16(cmapOffset + 2);
14241
+ for (let j = 0; j < subtables; j++) {
14242
+ const encOffset = cmapOffset + 4 + j * 8;
14243
+ if (encOffset + 8 > bytes.length) return false;
14244
+ const platform = u16(encOffset);
14245
+ const encoding = u16(encOffset + 2);
14246
+ if (platform === 0 || platform === 3 && (encoding === 1 || encoding === 10)) return true;
14247
+ }
14248
+ return false;
14249
+ }
14250
+ return false;
14251
+ }
14110
14252
  async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
14111
14253
  const fontFiles = FONT_FILES[fontName];
14112
14254
  if (!fontFiles) return false;
@@ -15814,7 +15956,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
15814
15956
  }
15815
15957
  if (shouldOutlineText) {
15816
15958
  try {
15817
- const { convertAllTextToPath } = await import("./svgTextToPath-B5sT7sre.js");
15959
+ const { convertAllTextToPath } = await import("./svgTextToPath-CShDi4Ky.js");
15818
15960
  pageSvg = await convertAllTextToPath(pageSvg, fontBaseUrl, { mode: outlineSubMode });
15819
15961
  try {
15820
15962
  dumpSvgTextDiagnostics(pageSvg, i, PARITY_TAG, "STAGE-1b-after-text-to-path-raw");