@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.
@@ -1009,7 +1009,7 @@ const defaultProjectSettings = {
1009
1009
  snapToGrid: false,
1010
1010
  gridSize: 20,
1011
1011
  snapToGuides: true,
1012
- snapThreshold: 5
1012
+ snapThreshold: 8
1013
1013
  };
1014
1014
  const defaultPageSettings = {
1015
1015
  backgroundColor: "#ffffff"
@@ -5737,7 +5737,7 @@ function getObjectSnapPoints(obj) {
5737
5737
  }
5738
5738
  function calculateSnapGuides(movingObj, canvas, canvasWidth, canvasHeight, snapToGuides, snapThreshold) {
5739
5739
  if (!snapToGuides) return { guides: [], snapDx: 0, snapDy: 0 };
5740
- const threshold = snapThreshold || 5;
5740
+ const threshold = snapThreshold || 8;
5741
5741
  const newGuides = [];
5742
5742
  const horizontalSnaps = [];
5743
5743
  const verticalSnaps = [];
@@ -5799,13 +5799,115 @@ function calculateSnapGuides(movingObj, canvas, canvasWidth, canvasHeight, snapT
5799
5799
  verticalSnaps.sort((a, b) => a.distance - b.distance);
5800
5800
  const bestSnap = verticalSnaps[0];
5801
5801
  snapDx = bestSnap.delta;
5802
- newGuides.push({ ...bestSnap.guide, distance: Math.round(bestSnap.distance) });
5802
+ newGuides.push({ ...bestSnap.guide, kind: "alignment" });
5803
5803
  }
5804
5804
  if (horizontalSnaps.length > 0) {
5805
5805
  horizontalSnaps.sort((a, b) => a.distance - b.distance);
5806
5806
  const bestSnap = horizontalSnaps[0];
5807
5807
  snapDy = bestSnap.delta;
5808
- newGuides.push({ ...bestSnap.guide, distance: Math.round(bestSnap.distance) });
5808
+ newGuides.push({ ...bestSnap.guide, kind: "alignment" });
5809
+ }
5810
+ const projectedLeft = moving.left + snapDx;
5811
+ const projectedRight = moving.right + snapDx;
5812
+ moving.centerX + snapDx;
5813
+ const projectedTop = moving.top + snapDy;
5814
+ const projectedBottom = moving.bottom + snapDy;
5815
+ moving.centerY + snapDy;
5816
+ if (snapDx === 0) {
5817
+ let bestPair = null;
5818
+ for (const a of allObjects) {
5819
+ const A = getObjectSnapPoints(a);
5820
+ if (A.right > projectedLeft) continue;
5821
+ if (A.bottom < projectedTop || A.top > projectedBottom) continue;
5822
+ for (const b of allObjects) {
5823
+ if (b === a) continue;
5824
+ const B = getObjectSnapPoints(b);
5825
+ if (B.left < projectedRight) continue;
5826
+ if (B.bottom < projectedTop || B.top > projectedBottom) continue;
5827
+ const gapL = projectedLeft - A.right;
5828
+ const gapR = B.left - projectedRight;
5829
+ const diff = gapR - gapL;
5830
+ const absDiff = Math.abs(diff);
5831
+ if (absDiff < threshold && (!bestPair || absDiff < bestPair.absDiff)) {
5832
+ bestPair = { A, B, delta: diff / 2, absDiff };
5833
+ }
5834
+ }
5835
+ }
5836
+ if (bestPair) {
5837
+ snapDx = bestPair.delta;
5838
+ const newLeft = moving.left + snapDx;
5839
+ const newRight = moving.right + snapDx;
5840
+ const equalGap = Math.round(newLeft - bestPair.A.right);
5841
+ const bracketY = (Math.max(bestPair.A.top, projectedTop, bestPair.B.top) + Math.min(bestPair.A.bottom, projectedBottom, bestPair.B.bottom)) / 2;
5842
+ if (equalGap > 0) {
5843
+ newGuides.push({
5844
+ type: "horizontal",
5845
+ position: bracketY,
5846
+ kind: "gap",
5847
+ gap: equalGap,
5848
+ start: bestPair.A.right,
5849
+ end: newLeft,
5850
+ bracketAt: bracketY
5851
+ });
5852
+ newGuides.push({
5853
+ type: "horizontal",
5854
+ position: bracketY,
5855
+ kind: "gap",
5856
+ gap: equalGap,
5857
+ start: newRight,
5858
+ end: bestPair.B.left,
5859
+ bracketAt: bracketY
5860
+ });
5861
+ }
5862
+ }
5863
+ }
5864
+ if (snapDy === 0) {
5865
+ let bestPair = null;
5866
+ for (const a of allObjects) {
5867
+ const A = getObjectSnapPoints(a);
5868
+ if (A.bottom > projectedTop) continue;
5869
+ if (A.right < projectedLeft || A.left > projectedRight) continue;
5870
+ for (const b of allObjects) {
5871
+ if (b === a) continue;
5872
+ const B = getObjectSnapPoints(b);
5873
+ if (B.top < projectedBottom) continue;
5874
+ if (B.right < projectedLeft || B.left > projectedRight) continue;
5875
+ const gapT = projectedTop - A.bottom;
5876
+ const gapB = B.top - projectedBottom;
5877
+ const diff = gapB - gapT;
5878
+ const absDiff = Math.abs(diff);
5879
+ if (absDiff < threshold && (!bestPair || absDiff < bestPair.absDiff)) {
5880
+ bestPair = { A, B, delta: diff / 2, absDiff };
5881
+ }
5882
+ }
5883
+ }
5884
+ if (bestPair) {
5885
+ snapDy = bestPair.delta;
5886
+ const newTop = moving.top + snapDy;
5887
+ const newBottom = moving.bottom + snapDy;
5888
+ const equalGap = Math.round(newTop - bestPair.A.bottom);
5889
+ const bracketX = (Math.max(bestPair.A.left, projectedLeft, bestPair.B.left) + Math.min(bestPair.A.right, projectedRight, bestPair.B.right)) / 2;
5890
+ if (equalGap > 0) {
5891
+ newGuides.push({
5892
+ type: "vertical",
5893
+ position: bracketX,
5894
+ kind: "gap",
5895
+ gap: equalGap,
5896
+ start: bestPair.A.bottom,
5897
+ end: newTop,
5898
+ bracketAt: bracketX
5899
+ });
5900
+ newGuides.push({
5901
+ type: "vertical",
5902
+ position: bracketX,
5903
+ kind: "gap",
5904
+ gap: equalGap,
5905
+ start: newBottom,
5906
+ end: bestPair.B.top,
5907
+ bracketAt: bracketX
5908
+ });
5909
+ }
5910
+ }
5809
5911
  }
5810
5912
  return { guides: newGuides, snapDx, snapDy };
5811
5913
  }
@@ -7442,6 +7544,31 @@ function buildRoundedRectPath2D(ctx, x, y, w, h, rTL, rTR, rBR, rBL) {
7442
7544
  if (tl > 0) ctx.quadraticCurveTo(x, y, x + tl, y);
7443
7545
  ctx.closePath();
7444
7546
  }
7547
+ function measureLineGlyphWidth(obj, lineIndex) {
7548
+ var _a2, _b, _c, _d, _e, _f;
7549
+ try {
7550
+ const rawLine = (_a2 = obj == null ? void 0 : obj._textLines) == null ? void 0 : _a2[lineIndex];
7551
+ const lineText = Array.isArray(rawLine) ? rawLine.join("") : String(rawLine ?? "");
7552
+ if (!lineText) return 0;
7553
+ const fontSize = Number(((_b = obj.getValueOfPropertyAt) == null ? void 0 : _b.call(obj, lineIndex, 0, "fontSize")) ?? obj.fontSize ?? 0);
7554
+ if (!fontSize) return 0;
7555
+ const fontStyle = String(((_c = obj.getValueOfPropertyAt) == null ? void 0 : _c.call(obj, lineIndex, 0, "fontStyle")) ?? obj.fontStyle ?? "normal");
7556
+ const fontWeight = String(((_d = obj.getValueOfPropertyAt) == null ? void 0 : _d.call(obj, lineIndex, 0, "fontWeight")) ?? obj.fontWeight ?? "400");
7557
+ const fontFamily = String(((_e = obj.getValueOfPropertyAt) == null ? void 0 : _e.call(obj, lineIndex, 0, "fontFamily")) ?? obj.fontFamily ?? "sans-serif");
7558
+ const charSpacing = Number(((_f = obj.getValueOfPropertyAt) == null ? void 0 : _f.call(obj, lineIndex, 0, "charSpacing")) ?? obj.charSpacing ?? 0);
7559
+ if (typeof document === "undefined") return 0;
7560
+ const off = document.createElement("canvas");
7561
+ const ctx = off.getContext("2d");
7562
+ if (!ctx) return 0;
7563
+ ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px ${fontFamily}`;
7564
+ const measured = ctx.measureText(lineText).width;
7565
+ const graphemeCount = Array.from(lineText).length;
7566
+ const spacingWidth = graphemeCount > 1 ? charSpacing / 1e3 * fontSize * (graphemeCount - 1) : 0;
7567
+ return Math.max(0, measured + spacingWidth);
7568
+ } catch {
7569
+ return 0;
7570
+ }
7571
+ }
7445
7572
  function applyTextBackground(obj, cfg) {
7446
7573
  var _a2;
7447
7574
  obj[PD_BG_KEY] = { ...cfg };
@@ -8046,6 +8173,10 @@ function computeBgRects(obj, w, h, pT, pR, pB, pL, fit, lineFullWidth = false) {
8046
8173
  } catch {
8047
8174
  lineH = 0;
8048
8175
  }
8176
+ if (fit && !lineFullWidth) {
8177
+ const measured = measureLineGlyphWidth(obj, i);
8178
+ if (measured > 0) lineW = measured;
8179
+ }
8049
8180
  const rawSlotH = i === lines.length - 1 ? lineH / lineHeightRatio : lineH;
8050
8181
  const usedH = cursorY + halfH;
8051
8182
  const remaining = h - usedH;
@@ -9910,6 +10041,7 @@ const PageCanvas = forwardRef(
9910
10041
  const hasClearedCachesBeforeFirstSyncRef = useRef(false);
9911
10042
  const [guides, setGuides] = useState([]);
9912
10043
  const [gridResizeLabel, setGridResizeLabel] = useState(null);
10044
+ const [hoverBounds, setHoverBounds] = useState(null);
9913
10045
  const [rotationLabel, setRotationLabel] = useState(null);
9914
10046
  const [sizeLabel, setSizeLabel] = useState(null);
9915
10047
  const [ready, setReady] = useState(false);
@@ -11251,6 +11383,26 @@ const PageCanvas = forwardRef(
11251
11383
  if (editLockRef.current) return;
11252
11384
  const t = opt.target;
11253
11385
  const tid = t ? getObjectId(t) : null;
11386
+ try {
11387
+ const activeIds = new Set(selectedIdsRef.current);
11388
+ const isHoveringSelected = !!(tid && activeIds.has(tid));
11389
+ if (t && tid && tid !== "__background__" && !isHoveringSelected) {
11390
+ const outer = t.group && !t.group.__pixldocsGroupSelection ? t.group : t;
11391
+ outer.setCoords();
11392
+ const br = outer.getBoundingRect();
11393
+ setHoverBounds({
11394
+ left: br.left,
11395
+ top: br.top,
11396
+ width: br.width,
11397
+ height: br.height,
11398
+ angle: 0
11399
+ });
11400
+ } else {
11401
+ setHoverBounds(null);
11402
+ }
11403
+ } catch {
11404
+ setHoverBounds(null);
11405
+ }
11254
11406
  if (t && tid && tid !== "__background__") return;
11255
11407
  try {
11256
11408
  const pointer = fabricCanvas.getPointer(opt.e);
@@ -11263,6 +11415,9 @@ const PageCanvas = forwardRef(
11263
11415
  fabricCanvas.defaultCursor = "default";
11264
11416
  }
11265
11417
  });
11418
+ fabricCanvas.on("mouse:out", () => {
11419
+ setHoverBounds(null);
11420
+ });
11266
11421
  fabricCanvas.on("mouse:down", (ev) => {
11267
11422
  if (fabricCanvas._currentTransform) {
11268
11423
  lockEdits();
@@ -11626,6 +11781,7 @@ const PageCanvas = forwardRef(
11626
11781
  const snapTarget = fabricCanvas.getActiveObject() ?? obj;
11627
11782
  const { guides: newGuides, snapDx, snapDy } = calculateSnapGuidesCallback(snapTarget);
11628
11783
  setGuides(newGuides);
11784
+ setHoverBounds(null);
11629
11785
  if (snapDx !== 0 || snapDy !== 0) {
11630
11786
  snapTarget.set({ left: (snapTarget.left ?? 0) + snapDx, top: (snapTarget.top ?? 0) + snapDy });
11631
11787
  }
@@ -15113,6 +15269,59 @@ const PageCanvas = forwardRef(
15113
15269
  ),
15114
15270
  sectionsOverlay,
15115
15271
  groupBoundsOverlay,
15272
+ canvas.projectSettings.showGrid && (canvas.projectSettings.gridSize ?? 0) > 0 && /* @__PURE__ */ jsxs(
15273
+ "svg",
15274
+ {
15275
+ className: "absolute inset-0 pointer-events-none",
15276
+ style: { width: scaledWidth, height: scaledHeight },
15277
+ viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
15278
+ children: [
15279
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
15280
+ "pattern",
15281
+ {
15282
+ id: `pixldocs-grid-${pageId}`,
15283
+ width: canvas.projectSettings.gridSize,
15284
+ height: canvas.projectSettings.gridSize,
15285
+ patternUnits: "userSpaceOnUse",
15286
+ children: /* @__PURE__ */ jsx(
15287
+ "path",
15288
+ {
15289
+ d: `M ${canvas.projectSettings.gridSize} 0 L 0 0 0 ${canvas.projectSettings.gridSize}`,
15290
+ fill: "none",
15291
+ stroke: "currentColor",
15292
+ strokeWidth: 0.5,
15293
+ opacity: 0.18,
15294
+ className: "text-foreground"
15295
+ }
15296
+ )
15297
+ }
15298
+ ) }),
15299
+ /* @__PURE__ */ jsx("rect", { width: canvasWidth, height: canvasHeight, fill: `url(#pixldocs-grid-${pageId})` })
15300
+ ]
15301
+ }
15302
+ ),
15303
+ hoverBounds && !guides.length && /* @__PURE__ */ jsx(
15304
+ "svg",
15305
+ {
15306
+ className: "absolute inset-0 pointer-events-none",
15307
+ style: { width: scaledWidth, height: scaledHeight },
15308
+ viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
15309
+ children: /* @__PURE__ */ jsx(
15310
+ "rect",
15311
+ {
15312
+ x: hoverBounds.left,
15313
+ y: hoverBounds.top,
15314
+ width: hoverBounds.width,
15315
+ height: hoverBounds.height,
15316
+ fill: "none",
15317
+ stroke: "hsl(256 80% 58%)",
15318
+ strokeWidth: 1,
15319
+ strokeDasharray: "0",
15320
+ opacity: 0.65
15321
+ }
15322
+ )
15323
+ }
15324
+ ),
15116
15325
  guides.length > 0 && /* @__PURE__ */ jsx(
15117
15326
  "svg",
15118
15327
  {
@@ -15122,11 +15331,46 @@ const PageCanvas = forwardRef(
15122
15331
  children: (() => {
15123
15332
  const seen = /* @__PURE__ */ new Set();
15124
15333
  return guides.filter((guide) => {
15334
+ if (guide.kind === "gap") return true;
15125
15335
  const key = `${guide.type}-${guide.position.toFixed(1)}`;
15126
15336
  if (seen.has(key)) return false;
15127
15337
  seen.add(key);
15128
15338
  return true;
15129
15339
  }).map((guide, i) => {
15340
+ if (guide.kind === "gap" && guide.gap != null && guide.start != null && guide.end != null) {
15341
+ const gapColor = "#ec4899";
15342
+ const label = `${guide.gap}`;
15343
+ const labelW2 = Math.max(22, label.length * 7 + 8);
15344
+ if (guide.type === "horizontal") {
15345
+ const y = guide.bracketAt ?? guide.position;
15346
+ const x1 = guide.start;
15347
+ const x2 = guide.end;
15348
+ const mid = (x1 + x2) / 2;
15349
+ return /* @__PURE__ */ jsxs("g", { children: [
15350
+ /* @__PURE__ */ jsx("line", { x1, y1: y, x2, y2: y, stroke: gapColor, strokeWidth: 1 }),
15351
+ /* @__PURE__ */ jsx("line", { x1, y1: y - 4, x2: x1, y2: y + 4, stroke: gapColor, strokeWidth: 1 }),
15352
+ /* @__PURE__ */ jsx("line", { x1: x2, y1: y - 4, x2, y2: y + 4, stroke: gapColor, strokeWidth: 1 }),
15353
+ /* @__PURE__ */ jsxs("g", { transform: `translate(${mid}, ${y - 12})`, children: [
15354
+ /* @__PURE__ */ jsx("rect", { x: -labelW2 / 2, y: -9, width: labelW2, height: 16, rx: 4, fill: gapColor }),
15355
+ /* @__PURE__ */ jsx("text", { x: 0, y: 3, textAnchor: "middle", fill: "#fff", fontSize: 10, fontFamily: "system-ui, sans-serif", fontWeight: 600, children: label })
15356
+ ] })
15357
+ ] }, i);
15358
+ } else {
15359
+ const x = guide.bracketAt ?? guide.position;
15360
+ const y1 = guide.start;
15361
+ const y2 = guide.end;
15362
+ const mid = (y1 + y2) / 2;
15363
+ return /* @__PURE__ */ jsxs("g", { children: [
15364
+ /* @__PURE__ */ jsx("line", { x1: x, y1, x2: x, y2, stroke: gapColor, strokeWidth: 1 }),
15365
+ /* @__PURE__ */ jsx("line", { x1: x - 4, y1, x2: x + 4, y2: y1, stroke: gapColor, strokeWidth: 1 }),
15366
+ /* @__PURE__ */ jsx("line", { x1: x - 4, y1: y2, x2: x + 4, y2, stroke: gapColor, strokeWidth: 1 }),
15367
+ /* @__PURE__ */ jsxs("g", { transform: `translate(${x + 12}, ${mid})`, children: [
15368
+ /* @__PURE__ */ jsx("rect", { x: -labelW2 / 2, y: -9, width: labelW2, height: 16, rx: 4, fill: gapColor }),
15369
+ /* @__PURE__ */ jsx("text", { x: 0, y: 3, textAnchor: "middle", fill: "#fff", fontSize: 10, fontFamily: "system-ui, sans-serif", fontWeight: 600, children: label })
15370
+ ] })
15371
+ ] }, i);
15372
+ }
15373
+ }
15130
15374
  const isElementRelative = guide.start !== void 0 && guide.end !== void 0;
15131
15375
  const isActive2 = guide.active === true;
15132
15376
  const strokeColor = isActive2 ? "#22c55e" : isElementRelative ? "#f43f5e" : "#3b82f6";
@@ -21121,9 +21365,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
21121
21365
  }
21122
21366
  return svgString;
21123
21367
  }
21124
- const resolvedPackageVersion = "0.5.239";
21368
+ const resolvedPackageVersion = "0.5.241";
21125
21369
  const PACKAGE_VERSION = resolvedPackageVersion;
21126
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.239";
21370
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.241";
21127
21371
  const roundParityValue = (value) => {
21128
21372
  if (typeof value !== "number") return value;
21129
21373
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -21870,7 +22114,7 @@ class PixldocsRenderer {
21870
22114
  await this.waitForCanvasScene(container, cloned, i);
21871
22115
  }
21872
22116
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
21873
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-DydXa9jD.js");
22117
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-BBY4avH9.js");
21874
22118
  const prepared = preparePagesForExport(
21875
22119
  cloned.pages,
21876
22120
  canvasWidth,
@@ -24190,7 +24434,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
24190
24434
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
24191
24435
  sanitizeSvgTreeForPdf(svgToDraw);
24192
24436
  try {
24193
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-DydXa9jD.js");
24437
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-BBY4avH9.js");
24194
24438
  try {
24195
24439
  await logTextMeasurementDiagnostic(svgToDraw);
24196
24440
  } catch {
@@ -24590,4 +24834,4 @@ export {
24590
24834
  buildTeaserBlurFlatKeys as y,
24591
24835
  collectFontDescriptorsFromConfig as z
24592
24836
  };
24593
- //# sourceMappingURL=index-DSRobH-5.js.map
24837
+ //# sourceMappingURL=index-DtyVze4W.js.map