Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import type { BezierPoint } from "../types";
 
// Calculate the bounding box of a cubic bezier curve
// Uses the derivative method to find extrema points
function getBezierCurveBoundingBox(
  p0: { x: number; y: number },
  p1: { x: number; y: number },
  p2: { x: number; y: number },
  p3: { x: number; y: number },
): { left: number; top: number; right: number; bottom: number } {
  // Start with the endpoints
  let minX = Math.min(p0.x, p3.x);
  let maxX = Math.max(p0.x, p3.x);
  let minY = Math.min(p0.y, p3.y);
  let maxY = Math.max(p0.y, p3.y);
 
  // Calculate coefficients for the derivative (for finding extrema)
  const ax = 3 * (p1.x - p0.x);
  const bx = 3 * (p2.x - p1.x) - ax;
  const cx = p3.x - p0.x - ax - bx;
 
  const ay = 3 * (p1.y - p0.y);
  const by = 3 * (p2.y - p1.y) - ay;
  const cy = p3.y - p0.y - ay - by;
 
  // Find extrema for x (where derivative = 0)
  const xExtrema = solveQuadratic(bx, 2 * ax, cx);
  for (const t of xExtrema) {
    if (t >= 0 && t <= 1) {
      const x =
        p0.x + t * (3 * (p1.x - p0.x) + t * (3 * (p2.x - 2 * p1.x + p0.x) + t * (p3.x - 3 * p2.x + 3 * p1.x - p0.x)));
      minX = Math.min(minX, x);
      maxX = Math.max(maxX, x);
    }
  }
 
  // Find extrema for y (where derivative = 0)
  const yExtrema = solveQuadratic(by, 2 * ay, cy);
  for (const t of yExtrema) {
    if (t >= 0 && t <= 1) {
      const y =
        p0.y + t * (3 * (p1.y - p0.y) + t * (3 * (p2.y - 2 * p1.y + p0.y) + t * (p3.y - 3 * p2.y + 3 * p1.y - p0.y)));
      minY = Math.min(minY, y);
      maxY = Math.max(maxY, y);
    }
  }
 
  return { left: minX, top: minY, right: maxX, bottom: maxY };
}
 
// Solve quadratic equation ax² + bx + c = 0
function solveQuadratic(a: number, b: number, c: number): number[] {
  if (Math.abs(a) < 1e-10) {
    // Linear equation
    if (Math.abs(b) < 1e-10) return [];
    return [-c / b];
  }
 
  const discriminant = b * b - 4 * a * c;
  if (discriminant < 0) return [];
 
  const sqrtDisc = Math.sqrt(discriminant);
  const solutions: number[] = [];
 
  const t1 = (-b + sqrtDisc) / (2 * a);
  if (t1 >= 0 && t1 <= 1) solutions.push(t1);
 
  const t2 = (-b - sqrtDisc) / (2 * a);
  if (t2 >= 0 && t2 <= 1) solutions.push(t2);
 
  return solutions;
}
 
// Calculate the bounding box of a shape including all bezier curves
export function calculateShapeBoundingBox(points: BezierPoint[]): {
  left: number;
  top: number;
  right: number;
  bottom: number;
} {
  if (points.length === 0) {
    return { left: 0, top: 0, right: 0, bottom: 0 };
  }
 
  let minX = Number.POSITIVE_INFINITY;
  let minY = Number.POSITIVE_INFINITY;
  let maxX = Number.NEGATIVE_INFINITY;
  let maxY = Number.NEGATIVE_INFINITY;
 
  // Process each segment
  for (let i = 0; i < points.length; i++) {
    const currentPoint = points[i];
    const nextPoint = points[(i + 1) % points.length];
 
    // Add the current point to the bounding box
    minX = Math.min(minX, currentPoint.x);
    minY = Math.min(minY, currentPoint.y);
    maxX = Math.max(maxX, currentPoint.x);
    maxY = Math.max(maxY, currentPoint.y);
 
    // If this is a bezier curve, calculate its bounding box
    if (currentPoint.isBezier && currentPoint.controlPoint1 && currentPoint.controlPoint2) {
      const curveBBox = getBezierCurveBoundingBox(
        { x: currentPoint.x, y: currentPoint.y },
        { x: currentPoint.controlPoint1.x, y: currentPoint.controlPoint1.y },
        { x: currentPoint.controlPoint2.x, y: currentPoint.controlPoint2.y },
        { x: nextPoint.x, y: nextPoint.y },
      );
 
      minX = Math.min(minX, curveBBox.left);
      minY = Math.min(minY, curveBBox.top);
      maxX = Math.max(maxX, curveBBox.right);
      maxY = Math.max(maxY, curveBBox.bottom);
    }
  }
 
  return { left: minX, top: minY, right: maxX, bottom: maxY };
}