Sfoglia il codice sorgente

Calculate rotated element bounds properly (#1354)

* Calculate rotated element bounds properly, fixes #1303

* prefer isLinearElement

* empty commit
Daishi Kato 5 anni fa
parent
commit
5ca763cdbb
1 ha cambiato i file con 102 aggiunte e 0 eliminazioni
  1. 102 0
      src/element/bounds.ts

+ 102 - 0
src/element/bounds.ts

@@ -197,12 +197,114 @@ export function getArrowPoints(
   return [x2, y2, x3, y3, x4, y4];
 }
 
+// this function has some code in common with getLinearElementAbsoluteBounds
+// there might be more efficient way
+const getLinearElementRotatedBounds = (
+  element: ExcalidrawLinearElement,
+  cx: number,
+  cy: number,
+): [number, number, number, number] => {
+  if (element.points.length < 2 || !getShapeForElement(element)) {
+    const { minX, minY, maxX, maxY } = element.points.reduce(
+      (limits, [x, y]) => {
+        [x, y] = rotate(element.x + x, element.y + y, cx, cy, element.angle);
+        limits.minY = Math.min(limits.minY, y);
+        limits.minX = Math.min(limits.minX, x);
+        limits.maxX = Math.max(limits.maxX, x);
+        limits.maxY = Math.max(limits.maxY, y);
+        return limits;
+      },
+      { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
+    );
+    return [minX, minY, maxX, maxY];
+  }
+
+  const shape = getShapeForElement(element) as Drawable[];
+
+  // first element is always the curve
+  const ops = getCurvePathOps(shape[0]);
+
+  let currentP: Point = [0, 0];
+
+  const { minX, minY, maxX, maxY } = ops.reduce(
+    (limits, { op, data }) => {
+      // There are only four operation types:
+      // move, bcurveTo, lineTo, and curveTo
+      if (op === "move") {
+        // change starting point
+        currentP = (data as unknown) as Point;
+        // move operation does not draw anything; so, it always
+        // returns false
+      } else if (op === "bcurveTo") {
+        // create points from bezier curve
+        // bezier curve stores data as a flattened array of three positions
+        // [x1, y1, x2, y2, x3, y3]
+        const p1 = [data[0], data[1]] as Point;
+        const p2 = [data[2], data[3]] as Point;
+        const p3 = [data[4], data[5]] as Point;
+
+        const p0 = currentP;
+        currentP = p3;
+
+        const equation = (t: number, idx: number) =>
+          Math.pow(1 - t, 3) * p3[idx] +
+          3 * t * Math.pow(1 - t, 2) * p2[idx] +
+          3 * Math.pow(t, 2) * (1 - t) * p1[idx] +
+          p0[idx] * Math.pow(t, 3);
+
+        let t = 0;
+        while (t <= 1.0) {
+          let x = equation(t, 0);
+          let y = equation(t, 1);
+          [x, y] = rotate(element.x + x, element.y + y, cx, cy, element.angle);
+          limits.minY = Math.min(limits.minY, y);
+          limits.minX = Math.min(limits.minX, x);
+          limits.maxX = Math.max(limits.maxX, x);
+          limits.maxY = Math.max(limits.maxY, y);
+          t += 0.1;
+        }
+      } else if (op === "lineTo") {
+        // TODO: Implement this
+      } else if (op === "qcurveTo") {
+        // TODO: Implement this
+      }
+      return limits;
+    },
+    { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
+  );
+
+  return [minX, minY, maxX, maxY];
+};
+
 export const getElementBounds = (
   element: ExcalidrawElement,
 ): [number, number, number, number] => {
   const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
   const cx = (x1 + x2) / 2;
   const cy = (y1 + y2) / 2;
+  if (isLinearElement(element)) {
+    return getLinearElementRotatedBounds(element, cx, cy);
+  }
+  if (element.type === "diamond") {
+    const [x11, y11] = rotate(cx, y1, cx, cy, element.angle);
+    const [x12, y12] = rotate(cx, y2, cx, cy, element.angle);
+    const [x22, y22] = rotate(x2, cy, cx, cy, element.angle);
+    const [x21, y21] = rotate(x2, cy, cx, cy, element.angle);
+    const minX = Math.min(x11, x12, x22, x21);
+    const minY = Math.min(y11, y12, y22, y21);
+    const maxX = Math.max(x11, x12, x22, x21);
+    const maxY = Math.max(y11, y12, y22, y21);
+    return [minX, minY, maxX, maxY];
+  }
+  if (element.type === "ellipse") {
+    const w = (x2 - x1) / 2;
+    const h = (y2 - y1) / 2;
+    const cos = Math.cos(element.angle);
+    const sin = Math.sin(element.angle);
+    const ww = Math.hypot(w * cos, h * sin);
+    const hh = Math.hypot(h * cos, w * sin);
+    return [cx - ww, cy - hh, cx + ww, cy + hh];
+  }
   const [x11, y11] = rotate(x1, y1, cx, cy, element.angle);
   const [x12, y12] = rotate(x1, y2, cx, cy, element.angle);
   const [x22, y22] = rotate(x2, y2, cx, cy, element.angle);