@pixldocs/canvas-renderer 0.5.398 → 0.5.400

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,20 @@ 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
- }
11638
- }
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
- if (targetAngle == null && dominant && Math.abs(dominant.angle) > 0.5 && (isLogicalGroupSelection || kids.length === 1 || dominant.count >= 2 || dominant.count === kids.length)) {
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;
11507
+ }
11508
+ if (targetAngle == null && dominant && Math.abs(dominant.angle) > 0.5 && // Canva-style: for a *logical group* the bbox angle is authoritative
11509
+ // from the persisted group.angle (frozenGroupAngle) only. Individual
11510
+ // child rotations must NEVER drift the group's selection bbox angle.
11511
+ // The dominant-angle fallback is reserved for ad-hoc multi-selects
11512
+ // (no logical group), where matching child rotation is desirable.
11513
+ (!isLogicalGroupSelection && (kids.length === 1 || dominant.count >= 2 || dominant.count === kids.length))) {
11644
11514
  targetAngle = dominant.angle;
11645
11515
  }
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
11516
  if (targetAngle != null) {
11655
11517
  const restoreKidsFromWorld = () => {
11656
11518
  const invSelection = fabric.util.invertTransform(
@@ -11689,30 +11551,11 @@ function applyWarpAwareSelectionBorders(selection, preferredGroupAngle, preferre
11689
11551
  targetAngle,
11690
11552
  worldAngles
11691
11553
  });
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
11554
  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
11555
  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
- );
11556
+ try {
11557
+ (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11558
+ } catch {
11716
11559
  }
11717
11560
  restoreKidsFromWorld();
11718
11561
  selection.setCoords();
@@ -11801,66 +11644,22 @@ const PageCanvas = forwardRef(
11801
11644
  const [ready, setReady] = useState(false);
11802
11645
  const [unlockRequestId, setUnlockRequestId] = useState(0);
11803
11646
  const applyLogicalGroupSelectionVisualState = useCallback((selection, groupId) => {
11804
- var _a2, _b2;
11647
+ var _a2;
11805
11648
  selection.__pixldocsGroupSelection = groupId;
11806
11649
  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;
11650
+ selection.hasBorders = true;
11651
+ try {
11652
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
11653
+ const node = pageNow ? findNodeById(pageNow.children ?? [], groupId) : null;
11654
+ const ang = node && isGroup(node) ? node.angle ?? 0 : 0;
11655
+ if (Math.abs(ang) > 0.01) {
11656
+ selection.__pixldocsFrozenGroupAngle = ang;
11657
+ } else {
11658
+ delete selection.__pixldocsFrozenGroupAngle;
11859
11659
  }
11860
- } else {
11861
- delete selection.__pixldocsGroupAngle;
11660
+ } catch {
11862
11661
  }
11863
- selection.hasBorders = true;
11662
+ const members = selection.getObjects();
11864
11663
  for (const prev of suppressGroupMemberBordersRef.current) {
11865
11664
  if (members.includes(prev)) continue;
11866
11665
  const origBorders = prev.__pixldocsOrigHasBorders;
@@ -11883,7 +11682,7 @@ const PageCanvas = forwardRef(
11883
11682
  if (m.__pixldocsOrigHasControls === void 0) m.__pixldocsOrigHasControls = m.hasControls;
11884
11683
  m.hasBorders = false;
11885
11684
  m.hasControls = false;
11886
- if (m.__cropGroup || ((_b2 = m._ct) == null ? void 0 : _b2.isCropGroup)) {
11685
+ if (m.__cropGroup || ((_a2 = m._ct) == null ? void 0 : _a2.isCropGroup)) {
11887
11686
  if (m.__pixldocsOrigLockScalingX === void 0) {
11888
11687
  m.__pixldocsOrigLockScalingX = m.lockScalingX;
11889
11688
  m.__pixldocsOrigLockScalingY = m.lockScalingY;
@@ -11892,8 +11691,8 @@ const PageCanvas = forwardRef(
11892
11691
  m.lockScalingY = false;
11893
11692
  }
11894
11693
  }
11895
- applyWarpAwareSelectionBorders(selection, groupAngle, groupFrame);
11896
- }, [pageId]);
11694
+ applyWarpAwareSelectionBorders(selection);
11695
+ }, []);
11897
11696
  const pageBoundsOptions = useMemo(
11898
11697
  () => ({ pageContentWidth: canvasWidth, pageContentHeight: canvasHeight }),
11899
11698
  [canvasWidth, canvasHeight]
@@ -11995,11 +11794,7 @@ const PageCanvas = forwardRef(
11995
11794
  const activeMembers = activeBeforeRestore.getObjects();
11996
11795
  const sameMembers = activeMembers.length === members.length && members.every((member) => activeMembers.includes(member));
11997
11796
  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);
11797
+ const alreadyAligned = activeBeforeRestore.__pixldocsAlignedAngle != null;
12003
11798
  if (sameMembers && sameGroup && alreadyAligned) {
12004
11799
  ensureCanvaControlRenders(activeBeforeRestore);
12005
11800
  return;
@@ -12016,11 +11811,9 @@ const PageCanvas = forwardRef(
12016
11811
  members.forEach((m) => m.setCoords());
12017
11812
  } catch {
12018
11813
  }
12019
- if (!selection.__pixldocsGroupSelection) {
12020
- try {
12021
- (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
12022
- } catch {
12023
- }
11814
+ try {
11815
+ (_a2 = selection.triggerLayout) == null ? void 0 : _a2.call(selection);
11816
+ } catch {
12024
11817
  }
12025
11818
  selection.setCoords();
12026
11819
  fc.requestRenderAll();
@@ -13460,18 +13253,14 @@ const PageCanvas = forwardRef(
13460
13253
  const groupNode = findNodeById(pageChildren2, groupId);
13461
13254
  if (!groupNode) return;
13462
13255
  const groupAbs = getAbsoluteBounds(groupNode, pageChildren2);
13463
- const storedGroupFrame = groupNode && isGroup(groupNode) ? frameFromStoredGroupNode(groupNode, pageChildren2, pageBoundsOptions) : null;
13464
- const groupFrame = storedGroupFrame ?? groupAbs;
13465
13256
  const rect = active.getBoundingRect();
13466
13257
  groupSelectionTransformStartRef.current = {
13467
13258
  groupId,
13468
13259
  selection: active,
13469
13260
  selectionLeft: rect.left,
13470
13261
  selectionTop: rect.top,
13471
- groupLeft: groupFrame.left,
13472
- groupTop: groupFrame.top,
13473
- groupWidth: groupFrame.width,
13474
- groupHeight: groupFrame.height,
13262
+ groupLeft: groupAbs.left,
13263
+ groupTop: groupAbs.top,
13475
13264
  selectionAngle: ((active.angle ?? 0) % 360 + 360) % 360
13476
13265
  };
13477
13266
  logRotDriftSelectionSnapshot("transform-start", active, {
@@ -13639,11 +13428,7 @@ const PageCanvas = forwardRef(
13639
13428
  const activeIds = active.getObjects().map((obj) => getObjectId(obj)).filter((id) => !!id && id !== "__background__");
13640
13429
  const sameMembers = activeIds.length === snapshot.memberIds.length && snapshot.memberIds.every((id) => activeIds.includes(id));
13641
13430
  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);
13431
+ const alreadyAligned = active.__pixldocsAlignedAngle != null;
13647
13432
  if (sameMembers && sameGroup && alreadyAligned) {
13648
13433
  ensureCanvaControlRenders(active);
13649
13434
  return;
@@ -13799,32 +13584,10 @@ const PageCanvas = forwardRef(
13799
13584
  if (oid && memberIds.has(oid)) members.push(o);
13800
13585
  }
13801
13586
  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
- }
13587
+ const norm = (a) => {
13588
+ const n = (a % 360 + 360) % 360;
13589
+ return n > 180 ? n - 360 : n;
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,13 @@ 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
- };
13855
- const buckets = [];
13856
- 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);
13859
- if (b) b.count++;
13860
- else buckets.push({ angle: a, count: 1, area: Number.POSITIVE_INFINITY });
13861
- }
13862
- for (const b of buckets) b.area = areaFor(b.angle);
13863
- 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();
13601
+ let a0;
13602
+ const savedGroupAngle = norm(g.angle ?? 0);
13603
+ if (Math.abs(savedGroupAngle) > 0.01) {
13604
+ a0 = savedGroupAngle;
13605
+ } else {
13606
+ a0 = 0;
13607
+ }
13867
13608
  const rad = -a0 * Math.PI / 180;
13868
13609
  const cos = Math.cos(rad), sin = Math.sin(rad);
13869
13610
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
@@ -15064,7 +14805,6 @@ const PageCanvas = forwardRef(
15064
14805
  fabricCanvas.on("object:rotating", (e) => {
15065
14806
  var _a2, _b2;
15066
14807
  markSimpleTransform(e);
15067
- prepareGroupSelectionTransformStart(e.target);
15068
14808
  didTransformRef.current = true;
15069
14809
  const tr = e.target;
15070
14810
  if (shouldLogRotDriftLiveTick(tr, "rotating")) {
@@ -15201,6 +14941,29 @@ const PageCanvas = forwardRef(
15201
14941
  } catch {
15202
14942
  }
15203
14943
  groupShiftReflowSnapshotRef.current = null;
14944
+ try {
14945
+ const t = e.target;
14946
+ if (t instanceof fabric.ActiveSelection) {
14947
+ const gid = t.__pixldocsGroupSelection;
14948
+ const delta = ((t.angle ?? 0) + 360) % 360;
14949
+ const deltaSigned = delta > 180 ? delta - 360 : delta;
14950
+ if (gid && Math.abs(deltaSigned) > 0.01) {
14951
+ const pageNow = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId);
14952
+ const node = pageNow ? findNodeById(pageNow.children ?? [], gid) : null;
14953
+ if (node && isGroup(node)) {
14954
+ const prev = node.angle ?? 0;
14955
+ const next = ((prev + deltaSigned) % 360 + 360) % 360;
14956
+ const nextSigned = next > 180 ? next - 360 : next;
14957
+ useEditorStore.getState().updateNode(
14958
+ gid,
14959
+ { angle: nextSigned },
14960
+ { recordHistory: false, skipLayoutRecalc: true }
14961
+ );
14962
+ }
14963
+ }
14964
+ }
14965
+ } catch {
14966
+ }
15204
14967
  lockEdits();
15205
14968
  const modifiedTarget = e.target;
15206
14969
  const modifiedTargetId = modifiedTarget ? getObjectId(modifiedTarget) : null;
@@ -15254,7 +15017,7 @@ const PageCanvas = forwardRef(
15254
15017
  const groupLeft = fabricCenterX - w / 2;
15255
15018
  const groupTop = fabricCenterY - h / 2;
15256
15019
  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 });
15020
+ useEditorStore.getState().updateNode(movedGroupId, { left: storePosGroup.left, top: storePosGroup.top }, { recordHistory: false, skipLayoutRecalc: true });
15258
15021
  }
15259
15022
  commitHistory();
15260
15023
  unlockEditsSoon();
@@ -15397,7 +15160,7 @@ const PageCanvas = forwardRef(
15397
15160
  const groupLeft = centerX - w / 2;
15398
15161
  const groupTop = centerY - h / 2;
15399
15162
  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 });
15163
+ useEditorStore.getState().updateNode(groupId, { left: storePosGroup.left, top: storePosGroup.top }, { recordHistory: false, skipLayoutRecalc: true });
15401
15164
  }
15402
15165
  const node = findNodeById(pageChildrenSec, groupId);
15403
15166
  if (isChildModified && node && !groupMoved) {
@@ -15426,7 +15189,7 @@ const PageCanvas = forwardRef(
15426
15189
  const groupLeft = active.originX === "center" ? centerX - w / 2 : centerX;
15427
15190
  const groupTop = active.originY === "center" ? centerY - h / 2 : centerY;
15428
15191
  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 });
15192
+ useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top }, { recordHistory: false, skipLayoutRecalc: true });
15430
15193
  commitHistory();
15431
15194
  unlockEditsSoon();
15432
15195
  return;
@@ -15608,38 +15371,6 @@ const PageCanvas = forwardRef(
15608
15371
  360 - Math.abs(currentSelAngle - startSelAngle)
15609
15372
  );
15610
15373
  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
15374
  if (!hadScale && !hadRotation && (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1)) {
15644
15375
  const { updateNode: updateNodeStore, commitHistory: commitHistoryStore, getCurrentElements } = useEditorStore.getState();
15645
15376
  const newLeft = (groupToMove.left ?? 0) + deltaX;
@@ -16275,11 +16006,9 @@ const PageCanvas = forwardRef(
16275
16006
  } catch {
16276
16007
  }
16277
16008
  fabricCanvas.setActiveObject(newSel);
16278
- if (!wasGroupSel) {
16279
- try {
16280
- (_j = newSel.triggerLayout) == null ? void 0 : _j.call(newSel);
16281
- } catch {
16282
- }
16009
+ try {
16010
+ (_j = newSel.triggerLayout) == null ? void 0 : _j.call(newSel);
16011
+ } catch {
16283
16012
  }
16284
16013
  try {
16285
16014
  for (const member of membersToReselect) member.setCoords();
@@ -16656,12 +16385,7 @@ const PageCanvas = forwardRef(
16656
16385
  const prevAS = activeBeforeSync instanceof fabric.ActiveSelection ? activeBeforeSync : null;
16657
16386
  const prevMembers = prevAS ? prevAS.getObjects() : [];
16658
16387
  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);
16388
+ const alreadyAligned = sameMembers && prevAS.__pixldocsAlignedAngle != null;
16665
16389
  if (sameMembers && alreadyAligned) {
16666
16390
  try {
16667
16391
  ensureCanvaControlRenders(prevAS);
@@ -17703,10 +17427,11 @@ const PageCanvas = forwardRef(
17703
17427
  if (sameSelection && (isFlatGroupSelection || isPureSingleGroupSelection)) {
17704
17428
  if (selectedGroupSelectionId && active instanceof fabric.ActiveSelection) {
17705
17429
  if (isPureSingleGroupSelection) {
17706
- applyLogicalGroupSelectionVisualState(active, selectedGroupSelectionId);
17430
+ active.__pixldocsGroupSelection = selectedGroupSelectionId;
17431
+ delete active.__pixldocsLogicalGroupIds;
17432
+ suppressGroupMemberBordersRef.current = active.getObjects();
17707
17433
  } else {
17708
17434
  delete active.__pixldocsGroupSelection;
17709
- delete active.__pixldocsGroupAngle;
17710
17435
  active.__pixldocsLogicalGroupIds = selectedGroupIds;
17711
17436
  active.hasBorders = true;
17712
17437
  suppressGroupMemberBordersRef.current = active.getObjects().filter((m) => {
@@ -17746,10 +17471,10 @@ const PageCanvas = forwardRef(
17746
17471
  const selection = new fabric.ActiveSelection(toSelect, { canvas: fc });
17747
17472
  if (selectedGroupSelectionId) {
17748
17473
  if (isPureSingleGroupSelection) {
17749
- applyLogicalGroupSelectionVisualState(selection, selectedGroupSelectionId);
17474
+ selection.__pixldocsGroupSelection = selectedGroupSelectionId;
17475
+ suppressGroupMemberBordersRef.current = toSelect;
17750
17476
  } else {
17751
17477
  selection.__pixldocsLogicalGroupIds = selectedGroupIds;
17752
- delete selection.__pixldocsGroupAngle;
17753
17478
  selection.hasBorders = true;
17754
17479
  suppressGroupMemberBordersRef.current = toSelect.filter((m) => {
17755
17480
  const id = getObjectId(m);
@@ -25392,9 +25117,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
25392
25117
  }
25393
25118
  return svgString;
25394
25119
  }
25395
- const resolvedPackageVersion = "0.5.398";
25120
+ const resolvedPackageVersion = "0.5.400";
25396
25121
  const PACKAGE_VERSION = resolvedPackageVersion;
25397
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.398";
25122
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.400";
25398
25123
  const roundParityValue = (value) => {
25399
25124
  if (typeof value !== "number") return value;
25400
25125
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -26208,7 +25933,7 @@ class PixldocsRenderer {
26208
25933
  await this.waitForCanvasScene(container, cloned, i);
26209
25934
  }
26210
25935
  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");
25936
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-DB5Ygq25.js");
26212
25937
  const prepared = preparePagesForExport(
26213
25938
  cloned.pages,
26214
25939
  canvasWidth,
@@ -28528,7 +28253,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
28528
28253
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
28529
28254
  sanitizeSvgTreeForPdf(svgToDraw);
28530
28255
  try {
28531
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-CtTnRtOo.js");
28256
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-DB5Ygq25.js");
28532
28257
  try {
28533
28258
  await logTextMeasurementDiagnostic(svgToDraw);
28534
28259
  } catch {
@@ -28928,4 +28653,4 @@ export {
28928
28653
  buildTeaserBlurFlatKeys as y,
28929
28654
  collectFontDescriptorsFromConfig as z
28930
28655
  };
28931
- //# sourceMappingURL=index-C4zpif_Z.js.map
28656
+ //# sourceMappingURL=index-B2tApyM3.js.map