@pixldocs/canvas-renderer 0.5.398 → 0.5.399

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.
@@ -107,7 +107,6 @@ const createDefaultGroup = (partial) => ({
107
107
  locked: false,
108
108
  left: 0,
109
109
  top: 0,
110
- angle: 0,
111
110
  ...partial
112
111
  });
113
112
  const PAGE_BACKGROUND_ELEMENT_ID = "__pageBackground__";
@@ -830,17 +829,9 @@ function getNodeBounds(node, pageChildren, options) {
830
829
  let width;
831
830
  let height;
832
831
  if (isGroup(node) && (pageChildren == null ? void 0 : pageChildren.length) !== void 0) {
833
- const group = node;
834
- const storedWidth = Number(group.width);
835
- const storedHeight = Number(group.height);
836
- if (!isStackLayoutMode(group.layoutMode) && storedWidth > 0 && storedHeight > 0) {
837
- width = storedWidth;
838
- height = storedHeight;
839
- } else {
840
- const size = groupBoundsFromChildren(group, pageChildren ?? []);
841
- width = size.width;
842
- height = size.height;
843
- }
832
+ const size = groupBoundsFromChildren(node, pageChildren ?? []);
833
+ width = size.width;
834
+ height = size.height;
844
835
  } else {
845
836
  width = simpleWidth(node);
846
837
  height = simpleHeight(node);
@@ -10511,119 +10502,6 @@ function bakeEdgeFade(source, fade) {
10511
10502
  }
10512
10503
  const SELECTION_PRIMARY = "hsl(217, 91%, 60%)";
10513
10504
  const SELECTION_BORDER_SCALE = 2;
10514
- const normalizeAngle180 = (angle) => {
10515
- const n = (angle % 360 + 360) % 360;
10516
- return n > 180 ? n - 360 : n;
10517
- };
10518
- const isValidLogicalGroupFrame = (frame) => !!frame && Number.isFinite(frame.left) && Number.isFinite(frame.top) && Number.isFinite(frame.width) && frame.width > 0 && Number.isFinite(frame.height) && frame.height > 0;
10519
- const frameFromStoredGroupNode = (group, pageChildren, options) => {
10520
- const width = Number(group.width);
10521
- const height = Number(group.height);
10522
- if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) return null;
10523
- try {
10524
- const abs = getAbsoluteBounds(group, pageChildren, options);
10525
- return { left: abs.left, top: abs.top, width, height };
10526
- } catch {
10527
- return null;
10528
- }
10529
- };
10530
- const orientedFrameFromWorldPoints = (points, angle) => {
10531
- if (points.length === 0 || !Number.isFinite(angle)) return null;
10532
- const rad = -angle * Math.PI / 180;
10533
- const cos = Math.cos(rad);
10534
- const sin = Math.sin(rad);
10535
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10536
- for (const p of points) {
10537
- const xr = p.x * cos - p.y * sin;
10538
- const yr = p.x * sin + p.y * cos;
10539
- minX = Math.min(minX, xr);
10540
- minY = Math.min(minY, yr);
10541
- maxX = Math.max(maxX, xr);
10542
- maxY = Math.max(maxY, yr);
10543
- }
10544
- if (![minX, minY, maxX, maxY].every(Number.isFinite)) return null;
10545
- const width = Math.max(1, maxX - minX);
10546
- const height = Math.max(1, maxY - minY);
10547
- const centerXRot = minX + width / 2;
10548
- const centerYRot = minY + height / 2;
10549
- const back = angle * Math.PI / 180;
10550
- const c = Math.cos(back);
10551
- const s = Math.sin(back);
10552
- const centerX = centerXRot * c - centerYRot * s;
10553
- const centerY = centerXRot * s + centerYRot * c;
10554
- return { left: centerX - width / 2, top: centerY - height / 2, width, height };
10555
- };
10556
- const collectFabricObjectWorldPoints = (objects) => {
10557
- const points = [];
10558
- for (const obj of objects) {
10559
- try {
10560
- obj.setCoords();
10561
- } catch {
10562
- }
10563
- const coords = typeof obj.getCoords === "function" ? obj.getCoords() : null;
10564
- if (Array.isArray(coords) && coords.length) {
10565
- points.push(...coords.map((p) => ({ x: p.x, y: p.y })));
10566
- continue;
10567
- }
10568
- const aC = obj.aCoords;
10569
- if (!aC) continue;
10570
- for (const key of ["tl", "tr", "br", "bl"]) {
10571
- const p = aC[key];
10572
- if (p) points.push({ x: p.x, y: p.y });
10573
- }
10574
- }
10575
- return points;
10576
- };
10577
- const collectFabricObjectWorldCenters = (objects) => {
10578
- var _a2;
10579
- const centers = [];
10580
- for (const obj of objects) {
10581
- try {
10582
- obj.setCoords();
10583
- } catch {
10584
- }
10585
- const coords = typeof obj.getCoords === "function" ? obj.getCoords() : null;
10586
- if (Array.isArray(coords) && coords.length >= 4) {
10587
- const pts = coords.slice(0, 4).map((p) => ({ x: Number(p.x), y: Number(p.y) })).filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
10588
- if (pts.length) centers.push({ x: pts.reduce((sum, p) => sum + p.x, 0) / pts.length, y: pts.reduce((sum, p) => sum + p.y, 0) / pts.length });
10589
- continue;
10590
- }
10591
- try {
10592
- const center = (_a2 = obj.getCenterPoint) == null ? void 0 : _a2.call(obj);
10593
- if (center && Number.isFinite(center.x) && Number.isFinite(center.y)) centers.push({ x: center.x, y: center.y });
10594
- } catch {
10595
- }
10596
- }
10597
- return centers;
10598
- };
10599
- const logicalFrameContainsWorldCenters = (frame, angle, centers) => {
10600
- if (!isValidLogicalGroupFrame(frame) || centers.length === 0) return true;
10601
- const a = typeof angle === "number" && Number.isFinite(angle) ? normalizeAngle180(angle) : 0;
10602
- const cx = frame.left + frame.width / 2;
10603
- const cy = frame.top + frame.height / 2;
10604
- const rad = -a * Math.PI / 180;
10605
- const c = Math.cos(rad);
10606
- const s = Math.sin(rad);
10607
- const tolerance = Math.max(8, Math.min(frame.width, frame.height) * 0.08);
10608
- return centers.every((p) => {
10609
- const dx = p.x - cx;
10610
- const dy = p.y - cy;
10611
- const localX = cx + dx * c - dy * s;
10612
- const localY = cy + dx * s + dy * c;
10613
- return localX >= frame.left - tolerance && localX <= frame.left + frame.width + tolerance && localY >= frame.top - tolerance && localY <= frame.top + frame.height + tolerance;
10614
- });
10615
- };
10616
- const shouldRepairUnrotatedLogicalGroupFrame = (storedFrame, contentFrame, angle) => {
10617
- if (!isValidLogicalGroupFrame(storedFrame) || !isValidLogicalGroupFrame(contentFrame)) return false;
10618
- if (Math.abs(normalizeAngle180(typeof angle === "number" && Number.isFinite(angle) ? angle : 0)) > 0.5) return false;
10619
- const storedArea = storedFrame.width * storedFrame.height;
10620
- const contentArea = contentFrame.width * contentFrame.height;
10621
- const areaRatio = storedArea / Math.max(1, contentArea);
10622
- const dx = Math.abs(storedFrame.left + storedFrame.width / 2 - (contentFrame.left + contentFrame.width / 2));
10623
- const dy = Math.abs(storedFrame.top + storedFrame.height / 2 - (contentFrame.top + contentFrame.height / 2));
10624
- const driftTolerance = Math.max(12, Math.min(storedFrame.width, storedFrame.height) * 0.08);
10625
- return areaRatio > 1.35 || dx > driftTolerance || dy > driftTolerance;
10626
- };
10627
10505
  let ensureCanvaControlRenders = () => {
10628
10506
  };
10629
10507
  try {
@@ -11554,7 +11432,7 @@ const bakeTextboxScaleIntoTypography = (obj, sourceElement) => {
11554
11432
  };
11555
11433
  return updates;
11556
11434
  };
11557
- function applyWarpAwareSelectionBorders(selection, preferredGroupAngle, preferredGroupFrame) {
11435
+ function applyWarpAwareSelectionBorders(selection) {
11558
11436
  var _a2;
11559
11437
  if (selection.__pixldocsOrigASHasBorders !== void 0) {
11560
11438
  selection.hasBorders = selection.__pixldocsOrigASHasBorders;
@@ -11566,13 +11444,17 @@ function applyWarpAwareSelectionBorders(selection, preferredGroupAngle, preferre
11566
11444
  if (selection.__pixldocsAlignedAngle == null) {
11567
11445
  const kids = selection.getObjects();
11568
11446
  if (kids.length >= 1) {
11569
- const angleDelta = (a, b) => Math.abs(normalizeAngle180(a - b));
11447
+ const norm = (a) => {
11448
+ const n = (a % 360 + 360) % 360;
11449
+ return n > 180 ? n - 360 : n;
11450
+ };
11451
+ const angleDelta = (a, b) => Math.abs(norm(a - b));
11570
11452
  const selectionMatrix = selection.calcTransformMatrix();
11571
11453
  const worldMatrices = kids.map((k) => fabric.util.multiplyTransformMatrices(
11572
11454
  selectionMatrix,
11573
11455
  k.calcOwnMatrix()
11574
11456
  ));
11575
- const worldAngles = worldMatrices.map((m) => normalizeAngle180(fabric.util.qrDecompose(m).angle ?? 0));
11457
+ const worldAngles = worldMatrices.map((m) => norm(fabric.util.qrDecompose(m).angle ?? 0));
11576
11458
  const worldPoints = [];
11577
11459
  for (const k of kids) {
11578
11460
  try {
@@ -11617,40 +11499,15 @@ function applyWarpAwareSelectionBorders(selection, preferredGroupAngle, preferre
11617
11499
  for (const b of buckets) b.area = orientedAreaForAngle(b.angle);
11618
11500
  buckets.sort((a, b) => b.count - a.count || a.area - b.area || Math.abs(b.angle) - Math.abs(a.angle));
11619
11501
  const dominant = buckets[0];
11620
- let effectivePreferredGroupAngle = preferredGroupAngle;
11621
- let effectivePreferredGroupFrame = preferredGroupFrame;
11622
- const logicalGroupId = selection.__pixldocsGroupSelection;
11623
- if (logicalGroupId && (!(typeof effectivePreferredGroupAngle === "number" && Number.isFinite(effectivePreferredGroupAngle)) || !isValidLogicalGroupFrame(effectivePreferredGroupFrame))) {
11624
- try {
11625
- const stateNow = useEditorStore.getState();
11626
- const pageNow = stateNow.canvas.pages.find((p) => p.id === stateNow.canvas.currentPageId);
11627
- const groupNode = pageNow ? findNodeById(pageNow.children ?? [], logicalGroupId) : null;
11628
- if (groupNode && isGroup(groupNode)) {
11629
- if (!(typeof effectivePreferredGroupAngle === "number" && Number.isFinite(effectivePreferredGroupAngle)) && typeof groupNode.angle === "number") {
11630
- effectivePreferredGroupAngle = groupNode.angle;
11631
- }
11632
- if (!isValidLogicalGroupFrame(effectivePreferredGroupFrame)) {
11633
- effectivePreferredGroupFrame = frameFromStoredGroupNode(groupNode, (pageNow == null ? void 0 : pageNow.children) ?? []);
11634
- }
11635
- }
11636
- } catch {
11637
- }
11502
+ let targetAngle = null;
11503
+ const isLogicalGroupSelection = !!selection.__pixldocsGroupSelection;
11504
+ const frozenGroupAngle = selection.__pixldocsFrozenGroupAngle;
11505
+ if (isLogicalGroupSelection && typeof frozenGroupAngle === "number" && Math.abs(frozenGroupAngle) > 0.5) {
11506
+ targetAngle = frozenGroupAngle;
11638
11507
  }
11639
- const rawPreferredGroupAngle = typeof effectivePreferredGroupAngle === "number" && Number.isFinite(effectivePreferredGroupAngle) ? effectivePreferredGroupAngle : typeof selection.__pixldocsGroupAngle === "number" && Number.isFinite(selection.__pixldocsGroupAngle) ? selection.__pixldocsGroupAngle : null;
11640
- const preferredAngle = rawPreferredGroupAngle != null ? normalizeAngle180(rawPreferredGroupAngle) : null;
11641
- let targetAngle = preferredAngle;
11642
- const isLogicalGroupSelection = !!logicalGroupId;
11643
11508
  if (targetAngle == null && dominant && Math.abs(dominant.angle) > 0.5 && (isLogicalGroupSelection || kids.length === 1 || dominant.count >= 2 || dominant.count === kids.length)) {
11644
11509
  targetAngle = dominant.angle;
11645
11510
  }
11646
- if (isLogicalGroupSelection && isValidLogicalGroupFrame(effectivePreferredGroupFrame) && targetAngle != null) {
11647
- const contentFrame = orientedFrameFromWorldPoints(worldPoints, targetAngle);
11648
- if (shouldRepairUnrotatedLogicalGroupFrame(effectivePreferredGroupFrame, contentFrame, targetAngle)) {
11649
- effectivePreferredGroupFrame = contentFrame;
11650
- } else if (!logicalFrameContainsWorldCenters(effectivePreferredGroupFrame, targetAngle, collectFabricObjectWorldCenters(kids))) {
11651
- effectivePreferredGroupFrame = null;
11652
- }
11653
- }
11654
11511
  if (targetAngle != null) {
11655
11512
  const restoreKidsFromWorld = () => {
11656
11513
  const invSelection = fabric.util.invertTransform(
@@ -11689,30 +11546,11 @@ function applyWarpAwareSelectionBorders(selection, preferredGroupAngle, preferre
11689
11546
  targetAngle,
11690
11547
  worldAngles
11691
11548
  });
11692
- const fixedFrame = effectivePreferredGroupFrame && Number.isFinite(effectivePreferredGroupFrame.left) && Number.isFinite(effectivePreferredGroupFrame.top) && Number.isFinite(effectivePreferredGroupFrame.width) && effectivePreferredGroupFrame.width > 0 && Number.isFinite(effectivePreferredGroupFrame.height) && effectivePreferredGroupFrame.height > 0 ? effectivePreferredGroupFrame : null;
11693
11549
  selection.set({ angle: targetAngle, scaleX: 1, scaleY: 1, skewX: 0, skewY: 0 });
11694
- if (fixedFrame) {
11695
- selection.set({ width: fixedFrame.width, height: fixedFrame.height, originX: "center", originY: "center" });
11696
- selection.setPositionByOrigin(
11697
- new fabric.Point(fixedFrame.left + fixedFrame.width / 2, fixedFrame.top + fixedFrame.height / 2),
11698
- "center",
11699
- "center"
11700
- );
11701
- }
11702
11550
  restoreKidsFromWorld();
11703
- if (!fixedFrame) {
11704
- try {
11705
- (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11706
- } catch {
11707
- }
11708
- }
11709
- if (fixedFrame) {
11710
- selection.set({ width: fixedFrame.width, height: fixedFrame.height, originX: "center", originY: "center" });
11711
- selection.setPositionByOrigin(
11712
- new fabric.Point(fixedFrame.left + fixedFrame.width / 2, fixedFrame.top + fixedFrame.height / 2),
11713
- "center",
11714
- "center"
11715
- );
11551
+ try {
11552
+ (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11553
+ } catch {
11716
11554
  }
11717
11555
  restoreKidsFromWorld();
11718
11556
  selection.setCoords();
@@ -11801,66 +11639,22 @@ const PageCanvas = forwardRef(
11801
11639
  const [ready, setReady] = useState(false);
11802
11640
  const [unlockRequestId, setUnlockRequestId] = useState(0);
11803
11641
  const applyLogicalGroupSelectionVisualState = useCallback((selection, groupId) => {
11804
- var _a2, _b2;
11642
+ var _a2;
11805
11643
  selection.__pixldocsGroupSelection = groupId;
11806
11644
  delete selection.__pixldocsLogicalGroupIds;
11807
- const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11808
- const groupNode = pageNow ? findNodeById(pageNow.children ?? [], groupId) : null;
11809
- let groupAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : void 0;
11810
- const members = selection.getObjects();
11811
- if (groupAngle === void 0 && groupNode && isGroup(groupNode)) {
11812
- const storedW = Number(groupNode.width);
11813
- const storedH = Number(groupNode.height);
11814
- if (storedW > 0 && storedH > 0) {
11815
- groupAngle = 0;
11816
- }
11817
- }
11818
- if (groupAngle === void 0 && groupNode && isGroup(groupNode)) {
11819
- try {
11820
- const selectionMatrix = selection.calcTransformMatrix();
11821
- const worldAngles = members.map((kid) => normalizeAngle180(fabric.util.qrDecompose(
11822
- fabric.util.multiplyTransformMatrices(
11823
- selectionMatrix,
11824
- kid.calcOwnMatrix()
11825
- )
11826
- ).angle ?? 0));
11827
- const buckets = [];
11828
- for (const angle of worldAngles) {
11829
- const bucket = buckets.find((candidate) => Math.abs(normalizeAngle180(candidate.angle - angle)) <= 2);
11830
- if (bucket) bucket.count += 1;
11831
- else buckets.push({ angle, count: 1 });
11832
- }
11833
- buckets.sort((a, b) => b.count - a.count || Math.abs(b.angle) - Math.abs(a.angle));
11834
- const inferredAngle = (_a2 = buckets[0]) == null ? void 0 : _a2.angle;
11835
- if (typeof inferredAngle === "number" && Number.isFinite(inferredAngle) && Math.abs(inferredAngle) > 0.01) {
11836
- groupAngle = inferredAngle;
11837
- }
11838
- } catch {
11839
- }
11840
- }
11841
- let groupFrame = groupNode && isGroup(groupNode) ? frameFromStoredGroupNode(groupNode, (pageNow == null ? void 0 : pageNow.children) ?? []) : null;
11842
- const memberWorldPoints = collectFabricObjectWorldPoints(members);
11843
- const memberWorldCenters = collectFabricObjectWorldCenters(members);
11844
- const contentFrame = typeof groupAngle === "number" && Number.isFinite(groupAngle) ? orientedFrameFromWorldPoints(memberWorldPoints, normalizeAngle180(groupAngle)) : null;
11845
- if (shouldRepairUnrotatedLogicalGroupFrame(groupFrame, contentFrame, groupAngle)) {
11846
- groupFrame = contentFrame;
11847
- } else if (groupFrame && !logicalFrameContainsWorldCenters(groupFrame, groupAngle, memberWorldCenters)) {
11848
- groupFrame = null;
11849
- }
11850
- if (!groupFrame && groupNode && isGroup(groupNode) && typeof groupAngle === "number" && Number.isFinite(groupAngle)) {
11851
- groupFrame = contentFrame ?? orientedFrameFromWorldPoints(memberWorldPoints, normalizeAngle180(groupAngle));
11852
- isValidLogicalGroupFrame(groupFrame);
11853
- }
11854
- if (groupAngle !== void 0) {
11855
- selection.__pixldocsGroupAngle = groupAngle;
11856
- const aligned = selection.__pixldocsAlignedAngle;
11857
- if (typeof aligned === "number" && Math.abs(normalizeAngle180(aligned - groupAngle)) > 0.01) {
11858
- delete selection.__pixldocsAlignedAngle;
11645
+ selection.hasBorders = true;
11646
+ try {
11647
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11648
+ const node = pageNow ? findNodeById(pageNow.children ?? [], groupId) : null;
11649
+ const ang = node && isGroup(node) ? node.angle ?? 0 : 0;
11650
+ if (Math.abs(ang) > 0.01) {
11651
+ selection.__pixldocsFrozenGroupAngle = ang;
11652
+ } else {
11653
+ delete selection.__pixldocsFrozenGroupAngle;
11859
11654
  }
11860
- } else {
11861
- delete selection.__pixldocsGroupAngle;
11655
+ } catch {
11862
11656
  }
11863
- selection.hasBorders = true;
11657
+ const members = selection.getObjects();
11864
11658
  for (const prev of suppressGroupMemberBordersRef.current) {
11865
11659
  if (members.includes(prev)) continue;
11866
11660
  const origBorders = prev.__pixldocsOrigHasBorders;
@@ -11883,7 +11677,7 @@ const PageCanvas = forwardRef(
11883
11677
  if (m.__pixldocsOrigHasControls === void 0) m.__pixldocsOrigHasControls = m.hasControls;
11884
11678
  m.hasBorders = false;
11885
11679
  m.hasControls = false;
11886
- if (m.__cropGroup || ((_b2 = m._ct) == null ? void 0 : _b2.isCropGroup)) {
11680
+ if (m.__cropGroup || ((_a2 = m._ct) == null ? void 0 : _a2.isCropGroup)) {
11887
11681
  if (m.__pixldocsOrigLockScalingX === void 0) {
11888
11682
  m.__pixldocsOrigLockScalingX = m.lockScalingX;
11889
11683
  m.__pixldocsOrigLockScalingY = m.lockScalingY;
@@ -11892,8 +11686,8 @@ const PageCanvas = forwardRef(
11892
11686
  m.lockScalingY = false;
11893
11687
  }
11894
11688
  }
11895
- applyWarpAwareSelectionBorders(selection, groupAngle, groupFrame);
11896
- }, [pageId]);
11689
+ applyWarpAwareSelectionBorders(selection);
11690
+ }, []);
11897
11691
  const pageBoundsOptions = useMemo(
11898
11692
  () => ({ pageContentWidth: canvasWidth, pageContentHeight: canvasHeight }),
11899
11693
  [canvasWidth, canvasHeight]
@@ -11995,11 +11789,7 @@ const PageCanvas = forwardRef(
11995
11789
  const activeMembers = activeBeforeRestore.getObjects();
11996
11790
  const sameMembers = activeMembers.length === members.length && members.every((member) => activeMembers.includes(member));
11997
11791
  const sameGroup = activeBeforeRestore.__pixldocsGroupSelection === groupId;
11998
- const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11999
- const groupNode = pageNow ? findNodeById(pageNow.children ?? [], groupId) : null;
12000
- const savedAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : null;
12001
- const alignedAngle = activeBeforeRestore.__pixldocsAlignedAngle;
12002
- const alreadyAligned = typeof alignedAngle === "number" && (savedAngle == null || Math.abs(normalizeAngle180(alignedAngle - savedAngle)) <= 0.01);
11792
+ const alreadyAligned = activeBeforeRestore.__pixldocsAlignedAngle != null;
12003
11793
  if (sameMembers && sameGroup && alreadyAligned) {
12004
11794
  ensureCanvaControlRenders(activeBeforeRestore);
12005
11795
  return;
@@ -12016,11 +11806,9 @@ const PageCanvas = forwardRef(
12016
11806
  members.forEach((m) => m.setCoords());
12017
11807
  } catch {
12018
11808
  }
12019
- if (!selection.__pixldocsGroupSelection) {
12020
- try {
12021
- (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
12022
- } catch {
12023
- }
11809
+ try {
11810
+ (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11811
+ } catch {
12024
11812
  }
12025
11813
  selection.setCoords();
12026
11814
  fc.requestRenderAll();
@@ -13460,18 +13248,14 @@ const PageCanvas = forwardRef(
13460
13248
  const groupNode = findNodeById(pageChildren2, groupId);
13461
13249
  if (!groupNode) return;
13462
13250
  const groupAbs = getAbsoluteBounds(groupNode, pageChildren2);
13463
- const storedGroupFrame = groupNode && isGroup(groupNode) ? frameFromStoredGroupNode(groupNode, pageChildren2, pageBoundsOptions) : null;
13464
- const groupFrame = storedGroupFrame ?? groupAbs;
13465
13251
  const rect = active.getBoundingRect();
13466
13252
  groupSelectionTransformStartRef.current = {
13467
13253
  groupId,
13468
13254
  selection: active,
13469
13255
  selectionLeft: rect.left,
13470
13256
  selectionTop: rect.top,
13471
- groupLeft: groupFrame.left,
13472
- groupTop: groupFrame.top,
13473
- groupWidth: groupFrame.width,
13474
- groupHeight: groupFrame.height,
13257
+ groupLeft: groupAbs.left,
13258
+ groupTop: groupAbs.top,
13475
13259
  selectionAngle: ((active.angle ?? 0) % 360 + 360) % 360
13476
13260
  };
13477
13261
  logRotDriftSelectionSnapshot("transform-start", active, {
@@ -13639,11 +13423,7 @@ const PageCanvas = forwardRef(
13639
13423
  const activeIds = active.getObjects().map((obj) => getObjectId(obj)).filter((id) => !!id && id !== "__background__");
13640
13424
  const sameMembers = activeIds.length === snapshot.memberIds.length && snapshot.memberIds.every((id) => activeIds.includes(id));
13641
13425
  const sameGroup = active.__pixldocsGroupSelection === snapshot.groupSelectionId;
13642
- const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
13643
- const groupNode = pageNow ? findNodeById(pageNow.children ?? [], snapshot.groupSelectionId) : null;
13644
- const savedAngle = groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : null;
13645
- const alignedAngle = active.__pixldocsAlignedAngle;
13646
- const alreadyAligned = typeof alignedAngle === "number" && (savedAngle == null || Math.abs(normalizeAngle180(alignedAngle - savedAngle)) <= 0.01);
13426
+ const alreadyAligned = active.__pixldocsAlignedAngle != null;
13647
13427
  if (sameMembers && sameGroup && alreadyAligned) {
13648
13428
  ensureCanvaControlRenders(active);
13649
13429
  return;
@@ -13799,32 +13579,15 @@ const PageCanvas = forwardRef(
13799
13579
  if (oid && memberIds.has(oid)) members.push(o);
13800
13580
  }
13801
13581
  if (members.length === 0) return null;
13802
- const savedGroupAngle = typeof g.angle === "number" && Number.isFinite(g.angle) ? normalizeAngle180(g.angle) : null;
13803
- const savedGroupWidth = Number(g.width);
13804
- const savedGroupHeight = Number(g.height);
13805
- if (savedGroupAngle != null && savedGroupWidth > 0 && savedGroupHeight > 0) {
13806
- const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
13807
- const pageChildrenNow = (pageNow == null ? void 0 : pageNow.children) ?? [];
13808
- const abs = getAbsoluteBounds(g, pageChildrenNow);
13809
- const cx = abs.left + savedGroupWidth / 2;
13810
- const cy = abs.top + savedGroupHeight / 2;
13811
- const rad2 = savedGroupAngle * Math.PI / 180;
13812
- const c = Math.cos(rad2), s = Math.sin(rad2);
13813
- const rotate = (x, y) => {
13814
- const dx = x - cx;
13815
- const dy = y - cy;
13816
- return { x: cx + dx * c - dy * s, y: cy + dx * s + dy * c };
13817
- };
13818
- return {
13819
- corners: [
13820
- rotate(abs.left, abs.top),
13821
- rotate(abs.left + savedGroupWidth, abs.top),
13822
- rotate(abs.left + savedGroupWidth, abs.top + savedGroupHeight),
13823
- rotate(abs.left, abs.top + savedGroupHeight)
13824
- ],
13825
- angle: savedGroupAngle
13826
- };
13827
- }
13582
+ const TOL = 2;
13583
+ const norm = (a) => {
13584
+ const n = (a % 360 + 360) % 360;
13585
+ return n > 180 ? n - 360 : n;
13586
+ };
13587
+ const angleDelta = (a, b) => {
13588
+ const d = Math.abs(norm(a) - norm(b));
13589
+ return Math.min(d, 360 - d);
13590
+ };
13828
13591
  const worldPoints = [];
13829
13592
  for (const m of members) {
13830
13593
  (_a2 = m.setCoords) == null ? void 0 : _a2.call(m);
@@ -13835,35 +13598,37 @@ const PageCanvas = forwardRef(
13835
13598
  if (p) worldPoints.push({ x: p.x, y: p.y });
13836
13599
  }
13837
13600
  }
13838
- const getLegacyDominantAngle = () => {
13839
- var _a3;
13840
- const areaFor = (angle) => {
13841
- if (worldPoints.length === 0) return Number.POSITIVE_INFINITY;
13842
- const r = -angle * Math.PI / 180;
13843
- const c = Math.cos(r), s = Math.sin(r);
13844
- let nX = Infinity, nY = Infinity, xX = -Infinity, xY = -Infinity;
13845
- for (const p of worldPoints) {
13846
- const xr = p.x * c - p.y * s;
13847
- const yr = p.x * s + p.y * c;
13848
- if (xr < nX) nX = xr;
13849
- if (yr < nY) nY = yr;
13850
- if (xr > xX) xX = xr;
13851
- if (yr > xY) xY = yr;
13852
- }
13853
- return Math.max(1, (xX - nX) * (xY - nY));
13854
- };
13601
+ const orientedAreaForAngle = (angle) => {
13602
+ if (worldPoints.length === 0) return Number.POSITIVE_INFINITY;
13603
+ const r = -angle * Math.PI / 180;
13604
+ const c = Math.cos(r), s = Math.sin(r);
13605
+ let nX = Infinity, nY = Infinity, xX = -Infinity, xY = -Infinity;
13606
+ for (const p of worldPoints) {
13607
+ const xr = p.x * c - p.y * s;
13608
+ const yr = p.x * s + p.y * c;
13609
+ if (xr < nX) nX = xr;
13610
+ if (yr < nY) nY = yr;
13611
+ if (xr > xX) xX = xr;
13612
+ if (yr > xY) xY = yr;
13613
+ }
13614
+ return Math.max(1, (xX - nX) * (xY - nY));
13615
+ };
13616
+ let a0;
13617
+ const savedGroupAngle = norm(g.angle ?? 0);
13618
+ if (Math.abs(savedGroupAngle) > 0.01) {
13619
+ a0 = savedGroupAngle;
13620
+ } else {
13855
13621
  const buckets = [];
13856
13622
  for (const m of members) {
13857
- const a = normalizeAngle180(m.angle ?? 0);
13858
- const b = buckets.find((x) => Math.abs(normalizeAngle180(x.angle - a)) <= 2);
13623
+ const a = norm(m.angle ?? 0);
13624
+ const b = buckets.find((x) => angleDelta(x.angle, a) <= TOL);
13859
13625
  if (b) b.count++;
13860
13626
  else buckets.push({ angle: a, count: 1, area: Number.POSITIVE_INFINITY });
13861
13627
  }
13862
- for (const b of buckets) b.area = areaFor(b.angle);
13628
+ for (const b of buckets) b.area = orientedAreaForAngle(b.angle);
13863
13629
  buckets.sort((a, b) => b.count - a.count || a.area - b.area || Math.abs(b.angle) - Math.abs(a.angle));
13864
- return ((_a3 = buckets[0]) == null ? void 0 : _a3.angle) ?? 0;
13865
- };
13866
- const a0 = savedGroupAngle ?? getLegacyDominantAngle();
13630
+ a0 = buckets[0].angle;
13631
+ }
13867
13632
  const rad = -a0 * Math.PI / 180;
13868
13633
  const cos = Math.cos(rad), sin = Math.sin(rad);
13869
13634
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
@@ -15064,7 +14829,6 @@ const PageCanvas = forwardRef(
15064
14829
  fabricCanvas.on("object:rotating", (e) => {
15065
14830
  var _a2, _b2;
15066
14831
  markSimpleTransform(e);
15067
- prepareGroupSelectionTransformStart(e.target);
15068
14832
  didTransformRef.current = true;
15069
14833
  const tr = e.target;
15070
14834
  if (shouldLogRotDriftLiveTick(tr, "rotating")) {
@@ -15201,6 +14965,29 @@ const PageCanvas = forwardRef(
15201
14965
  } catch {
15202
14966
  }
15203
14967
  groupShiftReflowSnapshotRef.current = null;
14968
+ try {
14969
+ const t = e.target;
14970
+ if (t instanceof fabric.ActiveSelection) {
14971
+ const gid = t.__pixldocsGroupSelection;
14972
+ const delta = ((t.angle ?? 0) + 360) % 360;
14973
+ const deltaSigned = delta > 180 ? delta - 360 : delta;
14974
+ if (gid && Math.abs(deltaSigned) > 0.01) {
14975
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
14976
+ const node = pageNow ? findNodeById(pageNow.children ?? [], gid) : null;
14977
+ if (node && isGroup(node)) {
14978
+ const prev = node.angle ?? 0;
14979
+ const next = ((prev + deltaSigned) % 360 + 360) % 360;
14980
+ const nextSigned = next > 180 ? next - 360 : next;
14981
+ useEditorStore.getState().updateNode(
14982
+ gid,
14983
+ { angle: nextSigned },
14984
+ { recordHistory: false, skipLayoutRecalc: true }
14985
+ );
14986
+ }
14987
+ }
14988
+ }
14989
+ } catch {
14990
+ }
15204
14991
  lockEdits();
15205
14992
  const modifiedTarget = e.target;
15206
14993
  const modifiedTargetId = modifiedTarget ? getObjectId(modifiedTarget) : null;
@@ -15254,7 +15041,7 @@ const PageCanvas = forwardRef(
15254
15041
  const groupLeft = fabricCenterX - w / 2;
15255
15042
  const groupTop = fabricCenterY - h / 2;
15256
15043
  const storePosGroup = absoluteToStorePosition(groupLeft, groupTop, movedGroupId, pageChildrenSec);
15257
- useEditorStore.getState().updateNode(movedGroupId, { left: storePosGroup.left, top: storePosGroup.top, angle: fabricSectionGroup.angle ?? 0 }, { recordHistory: false, skipLayoutRecalc: true });
15044
+ useEditorStore.getState().updateNode(movedGroupId, { left: storePosGroup.left, top: storePosGroup.top }, { recordHistory: false, skipLayoutRecalc: true });
15258
15045
  }
15259
15046
  commitHistory();
15260
15047
  unlockEditsSoon();
@@ -15397,7 +15184,7 @@ const PageCanvas = forwardRef(
15397
15184
  const groupLeft = centerX - w / 2;
15398
15185
  const groupTop = centerY - h / 2;
15399
15186
  const storePosGroup = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildrenSec);
15400
- useEditorStore.getState().updateNode(groupId, { left: storePosGroup.left, top: storePosGroup.top, angle: active.angle ?? 0 }, { recordHistory: false, skipLayoutRecalc: true });
15187
+ useEditorStore.getState().updateNode(groupId, { left: storePosGroup.left, top: storePosGroup.top }, { recordHistory: false, skipLayoutRecalc: true });
15401
15188
  }
15402
15189
  const node = findNodeById(pageChildrenSec, groupId);
15403
15190
  if (isChildModified && node && !groupMoved) {
@@ -15426,7 +15213,7 @@ const PageCanvas = forwardRef(
15426
15213
  const groupLeft = active.originX === "center" ? centerX - w / 2 : centerX;
15427
15214
  const groupTop = active.originY === "center" ? centerY - h / 2 : centerY;
15428
15215
  const storePos = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildren3);
15429
- useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top, angle: active.angle ?? 0 }, { recordHistory: false, skipLayoutRecalc: true });
15216
+ useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top }, { recordHistory: false, skipLayoutRecalc: true });
15430
15217
  commitHistory();
15431
15218
  unlockEditsSoon();
15432
15219
  return;
@@ -15608,38 +15395,6 @@ const PageCanvas = forwardRef(
15608
15395
  360 - Math.abs(currentSelAngle - startSelAngle)
15609
15396
  );
15610
15397
  const hadRotation = isActiveSelection && activeObj && angleDelta > 0.01;
15611
- if ((hadScale || hadRotation) && activeObj instanceof fabric.ActiveSelection && activeGroupSelectionId === groupToMove.id) {
15612
- const groupTransformUpdates = {};
15613
- if (hadRotation) {
15614
- groupTransformUpdates.angle = normalizeAngle180(currentSelAngle);
15615
- activeObj.__pixldocsGroupAngle = groupTransformUpdates.angle;
15616
- }
15617
- if (hadScale && (transformStart == null ? void 0 : transformStart.groupId) === groupToMove.id) {
15618
- const center = activeObj.getCenterPoint();
15619
- const nextWidth = Math.max(1, transformStart.groupWidth * Math.abs(activeObj.scaleX ?? 1));
15620
- const nextHeight = Math.max(1, transformStart.groupHeight * Math.abs(activeObj.scaleY ?? 1));
15621
- const storePos = absoluteToStorePosition(
15622
- center.x - nextWidth / 2,
15623
- center.y - nextHeight / 2,
15624
- groupToMove.id,
15625
- pageChildren2
15626
- );
15627
- groupTransformUpdates.left = storePos.left;
15628
- groupTransformUpdates.top = storePos.top;
15629
- groupTransformUpdates.width = nextWidth;
15630
- groupTransformUpdates.height = nextHeight;
15631
- } else if (!Number.isFinite(Number(groupToMove.width)) || !Number.isFinite(Number(groupToMove.height))) {
15632
- groupTransformUpdates.width = (transformStart == null ? void 0 : transformStart.groupWidth) ?? groupAbs.width;
15633
- groupTransformUpdates.height = (transformStart == null ? void 0 : transformStart.groupHeight) ?? groupAbs.height;
15634
- }
15635
- if (Object.keys(groupTransformUpdates).length > 0) {
15636
- useEditorStore.getState().updateNode(
15637
- groupToMove.id,
15638
- groupTransformUpdates,
15639
- { recordHistory: false, skipLayoutRecalc: true }
15640
- );
15641
- }
15642
- }
15643
15398
  if (!hadScale && !hadRotation && (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1)) {
15644
15399
  const { updateNode: updateNodeStore, commitHistory: commitHistoryStore, getCurrentElements } = useEditorStore.getState();
15645
15400
  const newLeft = (groupToMove.left ?? 0) + deltaX;
@@ -16275,11 +16030,9 @@ const PageCanvas = forwardRef(
16275
16030
  } catch {
16276
16031
  }
16277
16032
  fabricCanvas.setActiveObject(newSel);
16278
- if (!wasGroupSel) {
16279
- try {
16280
- (_j = newSel.triggerLayout) == null ? void 0 : _j.call(newSel);
16281
- } catch {
16282
- }
16033
+ try {
16034
+ (_j = newSel.triggerLayout) == null ? void 0 : _j.call(newSel);
16035
+ } catch {
16283
16036
  }
16284
16037
  try {
16285
16038
  for (const member of membersToReselect) member.setCoords();
@@ -16656,12 +16409,7 @@ const PageCanvas = forwardRef(
16656
16409
  const prevAS = activeBeforeSync instanceof fabric.ActiveSelection ? activeBeforeSync : null;
16657
16410
  const prevMembers = prevAS ? prevAS.getObjects() : [];
16658
16411
  const sameMembers = !!prevAS && prevMembers.length === freshMembers.length && prevMembers.every((m) => freshMembers.includes(m));
16659
- const savedSnapshotAngle = activeSelectionSnapshot.groupSelectionId ? (() => {
16660
- const groupNode = findNodeById(pageTree, activeSelectionSnapshot.groupSelectionId);
16661
- return groupNode && isGroup(groupNode) && typeof groupNode.angle === "number" ? groupNode.angle : null;
16662
- })() : null;
16663
- const prevAlignedAngle = sameMembers ? prevAS.__pixldocsAlignedAngle : null;
16664
- const alreadyAligned = sameMembers && typeof prevAlignedAngle === "number" && (savedSnapshotAngle == null || Math.abs(normalizeAngle180(prevAlignedAngle - savedSnapshotAngle)) <= 0.01);
16412
+ const alreadyAligned = sameMembers && prevAS.__pixldocsAlignedAngle != null;
16665
16413
  if (sameMembers && alreadyAligned) {
16666
16414
  try {
16667
16415
  ensureCanvaControlRenders(prevAS);
@@ -17703,10 +17451,11 @@ const PageCanvas = forwardRef(
17703
17451
  if (sameSelection && (isFlatGroupSelection || isPureSingleGroupSelection)) {
17704
17452
  if (selectedGroupSelectionId && active instanceof fabric.ActiveSelection) {
17705
17453
  if (isPureSingleGroupSelection) {
17706
- applyLogicalGroupSelectionVisualState(active, selectedGroupSelectionId);
17454
+ active.__pixldocsGroupSelection = selectedGroupSelectionId;
17455
+ delete active.__pixldocsLogicalGroupIds;
17456
+ suppressGroupMemberBordersRef.current = active.getObjects();
17707
17457
  } else {
17708
17458
  delete active.__pixldocsGroupSelection;
17709
- delete active.__pixldocsGroupAngle;
17710
17459
  active.__pixldocsLogicalGroupIds = selectedGroupIds;
17711
17460
  active.hasBorders = true;
17712
17461
  suppressGroupMemberBordersRef.current = active.getObjects().filter((m) => {
@@ -17746,10 +17495,10 @@ const PageCanvas = forwardRef(
17746
17495
  const selection = new fabric.ActiveSelection(toSelect, { canvas: fc });
17747
17496
  if (selectedGroupSelectionId) {
17748
17497
  if (isPureSingleGroupSelection) {
17749
- applyLogicalGroupSelectionVisualState(selection, selectedGroupSelectionId);
17498
+ selection.__pixldocsGroupSelection = selectedGroupSelectionId;
17499
+ suppressGroupMemberBordersRef.current = toSelect;
17750
17500
  } else {
17751
17501
  selection.__pixldocsLogicalGroupIds = selectedGroupIds;
17752
- delete selection.__pixldocsGroupAngle;
17753
17502
  selection.hasBorders = true;
17754
17503
  suppressGroupMemberBordersRef.current = toSelect.filter((m) => {
17755
17504
  const id = getObjectId(m);
@@ -25392,9 +25141,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
25392
25141
  }
25393
25142
  return svgString;
25394
25143
  }
25395
- const resolvedPackageVersion = "0.5.398";
25144
+ const resolvedPackageVersion = "0.5.399";
25396
25145
  const PACKAGE_VERSION = resolvedPackageVersion;
25397
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.398";
25146
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.399";
25398
25147
  const roundParityValue = (value) => {
25399
25148
  if (typeof value !== "number") return value;
25400
25149
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -26208,7 +25957,7 @@ class PixldocsRenderer {
26208
25957
  await this.waitForCanvasScene(container, cloned, i);
26209
25958
  }
26210
25959
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
26211
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-CtTnRtOo.js");
25960
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-CyA0vb5f.js");
26212
25961
  const prepared = preparePagesForExport(
26213
25962
  cloned.pages,
26214
25963
  canvasWidth,
@@ -28528,7 +28277,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
28528
28277
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
28529
28278
  sanitizeSvgTreeForPdf(svgToDraw);
28530
28279
  try {
28531
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-CtTnRtOo.js");
28280
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-CyA0vb5f.js");
28532
28281
  try {
28533
28282
  await logTextMeasurementDiagnostic(svgToDraw);
28534
28283
  } catch {
@@ -28928,4 +28677,4 @@ export {
28928
28677
  buildTeaserBlurFlatKeys as y,
28929
28678
  collectFontDescriptorsFromConfig as z
28930
28679
  };
28931
- //# sourceMappingURL=index-C4zpif_Z.js.map
28680
+ //# sourceMappingURL=index-yLA9pqpr.js.map