@pixldocs/canvas-renderer 0.5.239 → 0.5.241

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.
@@ -1027,7 +1027,7 @@ const defaultProjectSettings = {
1027
1027
  snapToGrid: false,
1028
1028
  gridSize: 20,
1029
1029
  snapToGuides: true,
1030
- snapThreshold: 5
1030
+ snapThreshold: 8
1031
1031
  };
1032
1032
  const defaultPageSettings = {
1033
1033
  backgroundColor: "#ffffff"
@@ -5755,7 +5755,7 @@ function getObjectSnapPoints(obj) {
5755
5755
  }
5756
5756
  function calculateSnapGuides(movingObj, canvas, canvasWidth, canvasHeight, snapToGuides, snapThreshold) {
5757
5757
  if (!snapToGuides) return { guides: [], snapDx: 0, snapDy: 0 };
5758
- const threshold = snapThreshold || 5;
5758
+ const threshold = snapThreshold || 8;
5759
5759
  const newGuides = [];
5760
5760
  const horizontalSnaps = [];
5761
5761
  const verticalSnaps = [];
@@ -5817,13 +5817,115 @@ function calculateSnapGuides(movingObj, canvas, canvasWidth, canvasHeight, snapT
5817
5817
  verticalSnaps.sort((a, b) => a.distance - b.distance);
5818
5818
  const bestSnap = verticalSnaps[0];
5819
5819
  snapDx = bestSnap.delta;
5820
- newGuides.push({ ...bestSnap.guide, distance: Math.round(bestSnap.distance) });
5820
+ newGuides.push({ ...bestSnap.guide, kind: "alignment" });
5821
5821
  }
5822
5822
  if (horizontalSnaps.length > 0) {
5823
5823
  horizontalSnaps.sort((a, b) => a.distance - b.distance);
5824
5824
  const bestSnap = horizontalSnaps[0];
5825
5825
  snapDy = bestSnap.delta;
5826
- newGuides.push({ ...bestSnap.guide, distance: Math.round(bestSnap.distance) });
5826
+ newGuides.push({ ...bestSnap.guide, kind: "alignment" });
5827
+ }
5828
+ const projectedLeft = moving.left + snapDx;
5829
+ const projectedRight = moving.right + snapDx;
5830
+ moving.centerX + snapDx;
5831
+ const projectedTop = moving.top + snapDy;
5832
+ const projectedBottom = moving.bottom + snapDy;
5833
+ moving.centerY + snapDy;
5834
+ if (snapDx === 0) {
5835
+ let bestPair = null;
5836
+ for (const a of allObjects) {
5837
+ const A = getObjectSnapPoints(a);
5838
+ if (A.right > projectedLeft) continue;
5839
+ if (A.bottom < projectedTop || A.top > projectedBottom) continue;
5840
+ for (const b of allObjects) {
5841
+ if (b === a) continue;
5842
+ const B = getObjectSnapPoints(b);
5843
+ if (B.left < projectedRight) continue;
5844
+ if (B.bottom < projectedTop || B.top > projectedBottom) continue;
5845
+ const gapL = projectedLeft - A.right;
5846
+ const gapR = B.left - projectedRight;
5847
+ const diff = gapR - gapL;
5848
+ const absDiff = Math.abs(diff);
5849
+ if (absDiff < threshold && (!bestPair || absDiff < bestPair.absDiff)) {
5850
+ bestPair = { A, B, delta: diff / 2, absDiff };
5851
+ }
5852
+ }
5853
+ }
5854
+ if (bestPair) {
5855
+ snapDx = bestPair.delta;
5856
+ const newLeft = moving.left + snapDx;
5857
+ const newRight = moving.right + snapDx;
5858
+ const equalGap = Math.round(newLeft - bestPair.A.right);
5859
+ const bracketY = (Math.max(bestPair.A.top, projectedTop, bestPair.B.top) + Math.min(bestPair.A.bottom, projectedBottom, bestPair.B.bottom)) / 2;
5860
+ if (equalGap > 0) {
5861
+ newGuides.push({
5862
+ type: "horizontal",
5863
+ position: bracketY,
5864
+ kind: "gap",
5865
+ gap: equalGap,
5866
+ start: bestPair.A.right,
5867
+ end: newLeft,
5868
+ bracketAt: bracketY
5869
+ });
5870
+ newGuides.push({
5871
+ type: "horizontal",
5872
+ position: bracketY,
5873
+ kind: "gap",
5874
+ gap: equalGap,
5875
+ start: newRight,
5876
+ end: bestPair.B.left,
5877
+ bracketAt: bracketY
5878
+ });
5879
+ }
5880
+ }
5881
+ }
5882
+ if (snapDy === 0) {
5883
+ let bestPair = null;
5884
+ for (const a of allObjects) {
5885
+ const A = getObjectSnapPoints(a);
5886
+ if (A.bottom > projectedTop) continue;
5887
+ if (A.right < projectedLeft || A.left > projectedRight) continue;
5888
+ for (const b of allObjects) {
5889
+ if (b === a) continue;
5890
+ const B = getObjectSnapPoints(b);
5891
+ if (B.top < projectedBottom) continue;
5892
+ if (B.right < projectedLeft || B.left > projectedRight) continue;
5893
+ const gapT = projectedTop - A.bottom;
5894
+ const gapB = B.top - projectedBottom;
5895
+ const diff = gapB - gapT;
5896
+ const absDiff = Math.abs(diff);
5897
+ if (absDiff < threshold && (!bestPair || absDiff < bestPair.absDiff)) {
5898
+ bestPair = { A, B, delta: diff / 2, absDiff };
5899
+ }
5900
+ }
5901
+ }
5902
+ if (bestPair) {
5903
+ snapDy = bestPair.delta;
5904
+ const newTop = moving.top + snapDy;
5905
+ const newBottom = moving.bottom + snapDy;
5906
+ const equalGap = Math.round(newTop - bestPair.A.bottom);
5907
+ const bracketX = (Math.max(bestPair.A.left, projectedLeft, bestPair.B.left) + Math.min(bestPair.A.right, projectedRight, bestPair.B.right)) / 2;
5908
+ if (equalGap > 0) {
5909
+ newGuides.push({
5910
+ type: "vertical",
5911
+ position: bracketX,
5912
+ kind: "gap",
5913
+ gap: equalGap,
5914
+ start: bestPair.A.bottom,
5915
+ end: newTop,
5916
+ bracketAt: bracketX
5917
+ });
5918
+ newGuides.push({
5919
+ type: "vertical",
5920
+ position: bracketX,
5921
+ kind: "gap",
5922
+ gap: equalGap,
5923
+ start: newBottom,
5924
+ end: bestPair.B.top,
5925
+ bracketAt: bracketX
5926
+ });
5927
+ }
5928
+ }
5827
5929
  }
5828
5930
  return { guides: newGuides, snapDx, snapDy };
5829
5931
  }
@@ -7460,6 +7562,31 @@ function buildRoundedRectPath2D(ctx, x, y, w, h, rTL, rTR, rBR, rBL) {
7460
7562
  if (tl > 0) ctx.quadraticCurveTo(x, y, x + tl, y);
7461
7563
  ctx.closePath();
7462
7564
  }
7565
+ function measureLineGlyphWidth(obj, lineIndex) {
7566
+ var _a2, _b, _c, _d, _e, _f;
7567
+ try {
7568
+ const rawLine = (_a2 = obj == null ? void 0 : obj._textLines) == null ? void 0 : _a2[lineIndex];
7569
+ const lineText = Array.isArray(rawLine) ? rawLine.join("") : String(rawLine ?? "");
7570
+ if (!lineText) return 0;
7571
+ const fontSize = Number(((_b = obj.getValueOfPropertyAt) == null ? void 0 : _b.call(obj, lineIndex, 0, "fontSize")) ?? obj.fontSize ?? 0);
7572
+ if (!fontSize) return 0;
7573
+ const fontStyle = String(((_c = obj.getValueOfPropertyAt) == null ? void 0 : _c.call(obj, lineIndex, 0, "fontStyle")) ?? obj.fontStyle ?? "normal");
7574
+ const fontWeight = String(((_d = obj.getValueOfPropertyAt) == null ? void 0 : _d.call(obj, lineIndex, 0, "fontWeight")) ?? obj.fontWeight ?? "400");
7575
+ const fontFamily = String(((_e = obj.getValueOfPropertyAt) == null ? void 0 : _e.call(obj, lineIndex, 0, "fontFamily")) ?? obj.fontFamily ?? "sans-serif");
7576
+ const charSpacing = Number(((_f = obj.getValueOfPropertyAt) == null ? void 0 : _f.call(obj, lineIndex, 0, "charSpacing")) ?? obj.charSpacing ?? 0);
7577
+ if (typeof document === "undefined") return 0;
7578
+ const off = document.createElement("canvas");
7579
+ const ctx = off.getContext("2d");
7580
+ if (!ctx) return 0;
7581
+ ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px ${fontFamily}`;
7582
+ const measured = ctx.measureText(lineText).width;
7583
+ const graphemeCount = Array.from(lineText).length;
7584
+ const spacingWidth = graphemeCount > 1 ? charSpacing / 1e3 * fontSize * (graphemeCount - 1) : 0;
7585
+ return Math.max(0, measured + spacingWidth);
7586
+ } catch {
7587
+ return 0;
7588
+ }
7589
+ }
7463
7590
  function applyTextBackground(obj, cfg) {
7464
7591
  var _a2;
7465
7592
  obj[PD_BG_KEY] = { ...cfg };
@@ -8064,6 +8191,10 @@ function computeBgRects(obj, w, h, pT, pR, pB, pL, fit, lineFullWidth = false) {
8064
8191
  } catch {
8065
8192
  lineH = 0;
8066
8193
  }
8194
+ if (fit && !lineFullWidth) {
8195
+ const measured = measureLineGlyphWidth(obj, i);
8196
+ if (measured > 0) lineW = measured;
8197
+ }
8067
8198
  const rawSlotH = i === lines.length - 1 ? lineH / lineHeightRatio : lineH;
8068
8199
  const usedH = cursorY + halfH;
8069
8200
  const remaining = h - usedH;
@@ -9928,6 +10059,7 @@ const PageCanvas = react.forwardRef(
9928
10059
  const hasClearedCachesBeforeFirstSyncRef = react.useRef(false);
9929
10060
  const [guides, setGuides] = react.useState([]);
9930
10061
  const [gridResizeLabel, setGridResizeLabel] = react.useState(null);
10062
+ const [hoverBounds, setHoverBounds] = react.useState(null);
9931
10063
  const [rotationLabel, setRotationLabel] = react.useState(null);
9932
10064
  const [sizeLabel, setSizeLabel] = react.useState(null);
9933
10065
  const [ready, setReady] = react.useState(false);
@@ -11269,6 +11401,26 @@ const PageCanvas = react.forwardRef(
11269
11401
  if (editLockRef.current) return;
11270
11402
  const t = opt.target;
11271
11403
  const tid = t ? getObjectId(t) : null;
11404
+ try {
11405
+ const activeIds = new Set(selectedIdsRef.current);
11406
+ const isHoveringSelected = !!(tid && activeIds.has(tid));
11407
+ if (t && tid && tid !== "__background__" && !isHoveringSelected) {
11408
+ const outer = t.group && !t.group.__pixldocsGroupSelection ? t.group : t;
11409
+ outer.setCoords();
11410
+ const br = outer.getBoundingRect();
11411
+ setHoverBounds({
11412
+ left: br.left,
11413
+ top: br.top,
11414
+ width: br.width,
11415
+ height: br.height,
11416
+ angle: 0
11417
+ });
11418
+ } else {
11419
+ setHoverBounds(null);
11420
+ }
11421
+ } catch {
11422
+ setHoverBounds(null);
11423
+ }
11272
11424
  if (t && tid && tid !== "__background__") return;
11273
11425
  try {
11274
11426
  const pointer = fabricCanvas.getPointer(opt.e);
@@ -11281,6 +11433,9 @@ const PageCanvas = react.forwardRef(
11281
11433
  fabricCanvas.defaultCursor = "default";
11282
11434
  }
11283
11435
  });
11436
+ fabricCanvas.on("mouse:out", () => {
11437
+ setHoverBounds(null);
11438
+ });
11284
11439
  fabricCanvas.on("mouse:down", (ev) => {
11285
11440
  if (fabricCanvas._currentTransform) {
11286
11441
  lockEdits();
@@ -11644,6 +11799,7 @@ const PageCanvas = react.forwardRef(
11644
11799
  const snapTarget = fabricCanvas.getActiveObject() ?? obj;
11645
11800
  const { guides: newGuides, snapDx, snapDy } = calculateSnapGuidesCallback(snapTarget);
11646
11801
  setGuides(newGuides);
11802
+ setHoverBounds(null);
11647
11803
  if (snapDx !== 0 || snapDy !== 0) {
11648
11804
  snapTarget.set({ left: (snapTarget.left ?? 0) + snapDx, top: (snapTarget.top ?? 0) + snapDy });
11649
11805
  }
@@ -15131,6 +15287,59 @@ const PageCanvas = react.forwardRef(
15131
15287
  ),
15132
15288
  sectionsOverlay,
15133
15289
  groupBoundsOverlay,
15290
+ canvas.projectSettings.showGrid && (canvas.projectSettings.gridSize ?? 0) > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
15291
+ "svg",
15292
+ {
15293
+ className: "absolute inset-0 pointer-events-none",
15294
+ style: { width: scaledWidth, height: scaledHeight },
15295
+ viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
15296
+ children: [
15297
+ /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx(
15298
+ "pattern",
15299
+ {
15300
+ id: `pixldocs-grid-${pageId}`,
15301
+ width: canvas.projectSettings.gridSize,
15302
+ height: canvas.projectSettings.gridSize,
15303
+ patternUnits: "userSpaceOnUse",
15304
+ children: /* @__PURE__ */ jsxRuntime.jsx(
15305
+ "path",
15306
+ {
15307
+ d: `M ${canvas.projectSettings.gridSize} 0 L 0 0 0 ${canvas.projectSettings.gridSize}`,
15308
+ fill: "none",
15309
+ stroke: "currentColor",
15310
+ strokeWidth: 0.5,
15311
+ opacity: 0.18,
15312
+ className: "text-foreground"
15313
+ }
15314
+ )
15315
+ }
15316
+ ) }),
15317
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: canvasWidth, height: canvasHeight, fill: `url(#pixldocs-grid-${pageId})` })
15318
+ ]
15319
+ }
15320
+ ),
15321
+ hoverBounds && !guides.length && /* @__PURE__ */ jsxRuntime.jsx(
15322
+ "svg",
15323
+ {
15324
+ className: "absolute inset-0 pointer-events-none",
15325
+ style: { width: scaledWidth, height: scaledHeight },
15326
+ viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
15327
+ children: /* @__PURE__ */ jsxRuntime.jsx(
15328
+ "rect",
15329
+ {
15330
+ x: hoverBounds.left,
15331
+ y: hoverBounds.top,
15332
+ width: hoverBounds.width,
15333
+ height: hoverBounds.height,
15334
+ fill: "none",
15335
+ stroke: "hsl(256 80% 58%)",
15336
+ strokeWidth: 1,
15337
+ strokeDasharray: "0",
15338
+ opacity: 0.65
15339
+ }
15340
+ )
15341
+ }
15342
+ ),
15134
15343
  guides.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
15135
15344
  "svg",
15136
15345
  {
@@ -15140,11 +15349,46 @@ const PageCanvas = react.forwardRef(
15140
15349
  children: (() => {
15141
15350
  const seen = /* @__PURE__ */ new Set();
15142
15351
  return guides.filter((guide) => {
15352
+ if (guide.kind === "gap") return true;
15143
15353
  const key = `${guide.type}-${guide.position.toFixed(1)}`;
15144
15354
  if (seen.has(key)) return false;
15145
15355
  seen.add(key);
15146
15356
  return true;
15147
15357
  }).map((guide, i) => {
15358
+ if (guide.kind === "gap" && guide.gap != null && guide.start != null && guide.end != null) {
15359
+ const gapColor = "#ec4899";
15360
+ const label = `${guide.gap}`;
15361
+ const labelW2 = Math.max(22, label.length * 7 + 8);
15362
+ if (guide.type === "horizontal") {
15363
+ const y = guide.bracketAt ?? guide.position;
15364
+ const x1 = guide.start;
15365
+ const x2 = guide.end;
15366
+ const mid = (x1 + x2) / 2;
15367
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
15368
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1, y1: y, x2, y2: y, stroke: gapColor, strokeWidth: 1 }),
15369
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1, y1: y - 4, x2: x1, y2: y + 4, stroke: gapColor, strokeWidth: 1 }),
15370
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: x2, y1: y - 4, x2, y2: y + 4, stroke: gapColor, strokeWidth: 1 }),
15371
+ /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(${mid}, ${y - 12})`, children: [
15372
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: -labelW2 / 2, y: -9, width: labelW2, height: 16, rx: 4, fill: gapColor }),
15373
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: 3, textAnchor: "middle", fill: "#fff", fontSize: 10, fontFamily: "system-ui, sans-serif", fontWeight: 600, children: label })
15374
+ ] })
15375
+ ] }, i);
15376
+ } else {
15377
+ const x = guide.bracketAt ?? guide.position;
15378
+ const y1 = guide.start;
15379
+ const y2 = guide.end;
15380
+ const mid = (y1 + y2) / 2;
15381
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
15382
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: x, y1, x2: x, y2, stroke: gapColor, strokeWidth: 1 }),
15383
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: x - 4, y1, x2: x + 4, y2: y1, stroke: gapColor, strokeWidth: 1 }),
15384
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: x - 4, y1: y2, x2: x + 4, y2, stroke: gapColor, strokeWidth: 1 }),
15385
+ /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(${x + 12}, ${mid})`, children: [
15386
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: -labelW2 / 2, y: -9, width: labelW2, height: 16, rx: 4, fill: gapColor }),
15387
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: 3, textAnchor: "middle", fill: "#fff", fontSize: 10, fontFamily: "system-ui, sans-serif", fontWeight: 600, children: label })
15388
+ ] })
15389
+ ] }, i);
15390
+ }
15391
+ }
15148
15392
  const isElementRelative = guide.start !== void 0 && guide.end !== void 0;
15149
15393
  const isActive2 = guide.active === true;
15150
15394
  const strokeColor = isActive2 ? "#22c55e" : isElementRelative ? "#f43f5e" : "#3b82f6";
@@ -21139,9 +21383,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
21139
21383
  }
21140
21384
  return svgString;
21141
21385
  }
21142
- const resolvedPackageVersion = "0.5.239";
21386
+ const resolvedPackageVersion = "0.5.241";
21143
21387
  const PACKAGE_VERSION = resolvedPackageVersion;
21144
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.239";
21388
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.241";
21145
21389
  const roundParityValue = (value) => {
21146
21390
  if (typeof value !== "number") return value;
21147
21391
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -21888,7 +22132,7 @@ class PixldocsRenderer {
21888
22132
  await this.waitForCanvasScene(container, cloned, i);
21889
22133
  }
21890
22134
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
21891
- const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-ChKqZ9Rs.cjs"));
22135
+ const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-BQhOl3Zh.cjs"));
21892
22136
  const prepared = preparePagesForExport(
21893
22137
  cloned.pages,
21894
22138
  canvasWidth,
@@ -24208,7 +24452,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
24208
24452
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
24209
24453
  sanitizeSvgTreeForPdf(svgToDraw);
24210
24454
  try {
24211
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-ChKqZ9Rs.cjs"));
24455
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-BQhOl3Zh.cjs"));
24212
24456
  try {
24213
24457
  await logTextMeasurementDiagnostic(svgToDraw);
24214
24458
  } catch {
@@ -24605,4 +24849,4 @@ exports.setAutoShrinkDebug = setAutoShrinkDebug;
24605
24849
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
24606
24850
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
24607
24851
  exports.warmTemplateFromForm = warmTemplateFromForm;
24608
- //# sourceMappingURL=index-BScjmXRG.cjs.map
24852
+ //# sourceMappingURL=index-BRI84Wy6.cjs.map