bbox.js
  1  // The Bounding Box object
  2  
  3  function derive(v0, v1, v2, v3, t) {
  4      return Math.pow(1 - t, 3) * v0 +
  5          3 * Math.pow(1 - t, 2) * t * v1 +
  6          3 * (1 - t) * Math.pow(t, 2) * v2 +
  7          Math.pow(t, 3) * v3;
  8  }
  9  /**
 10   * A bounding box is an enclosing box that describes the smallest measure within which all the points lie.
 11   * It is used to calculate the bounding box of a glyph or text path.
 12   *
 13   * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`.
 14   *
 15   * @exports opentype.BoundingBox
 16   * @class
 17   * @constructor
 18   */
 19  function BoundingBox() {
 20      this.x1 = Number.NaN;
 21      this.y1 = Number.NaN;
 22      this.x2 = Number.NaN;
 23      this.y2 = Number.NaN;
 24  }
 25  
 26  /**
 27   * Returns true if the bounding box is empty, that is, no points have been added to the box yet.
 28   */
 29  BoundingBox.prototype.isEmpty = function() {
 30      return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2);
 31  };
 32  
 33  /**
 34   * Add the point to the bounding box.
 35   * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point.
 36   * @param {number} x - The X coordinate of the point.
 37   * @param {number} y - The Y coordinate of the point.
 38   */
 39  BoundingBox.prototype.addPoint = function(x, y) {
 40      if (typeof x === 'number') {
 41          if (isNaN(this.x1) || isNaN(this.x2)) {
 42              this.x1 = x;
 43              this.x2 = x;
 44          }
 45          if (x < this.x1) {
 46              this.x1 = x;
 47          }
 48          if (x > this.x2) {
 49              this.x2 = x;
 50          }
 51      }
 52      if (typeof y === 'number') {
 53          if (isNaN(this.y1) || isNaN(this.y2)) {
 54              this.y1 = y;
 55              this.y2 = y;
 56          }
 57          if (y < this.y1) {
 58              this.y1 = y;
 59          }
 60          if (y > this.y2) {
 61              this.y2 = y;
 62          }
 63      }
 64  };
 65  
 66  /**
 67   * Add a X coordinate to the bounding box.
 68   * This extends the bounding box to include the X coordinate.
 69   * This function is used internally inside of addBezier.
 70   * @param {number} x - The X coordinate of the point.
 71   */
 72  BoundingBox.prototype.addX = function(x) {
 73      this.addPoint(x, null);
 74  };
 75  
 76  /**
 77   * Add a Y coordinate to the bounding box.
 78   * This extends the bounding box to include the Y coordinate.
 79   * This function is used internally inside of addBezier.
 80   * @param {number} y - The Y coordinate of the point.
 81   */
 82  BoundingBox.prototype.addY = function(y) {
 83      this.addPoint(null, y);
 84  };
 85  
 86  /**
 87   * Add a Bézier curve to the bounding box.
 88   * This extends the bounding box to include the entire Bézier.
 89   * @param {number} x0 - The starting X coordinate.
 90   * @param {number} y0 - The starting Y coordinate.
 91   * @param {number} x1 - The X coordinate of the first control point.
 92   * @param {number} y1 - The Y coordinate of the first control point.
 93   * @param {number} x2 - The X coordinate of the second control point.
 94   * @param {number} y2 - The Y coordinate of the second control point.
 95   * @param {number} x - The ending X coordinate.
 96   * @param {number} y - The ending Y coordinate.
 97   */
 98  BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) {
 99      // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html
100      // and https://github.com/icons8/svg-path-bounding-box
101  
102      const p0 = [x0, y0];
103      const p1 = [x1, y1];
104      const p2 = [x2, y2];
105      const p3 = [x, y];
106  
107      this.addPoint(x0, y0);
108      this.addPoint(x, y);
109  
110      for (let i = 0; i <= 1; i++) {
111          const b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
112          const a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
113          const c = 3 * p1[i] - 3 * p0[i];
114  
115          if (a === 0) {
116              if (b === 0) continue;
117              const t = -c / b;
118              if (0 < t && t < 1) {
119                  if (i === 0) this.addX(derive(p0[i], p1[i], p2[i], p3[i], t));
120                  if (i === 1) this.addY(derive(p0[i], p1[i], p2[i], p3[i], t));
121              }
122              continue;
123          }
124  
125          const b2ac = Math.pow(b, 2) - 4 * c * a;
126          if (b2ac < 0) continue;
127          const t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
128          if (0 < t1 && t1 < 1) {
129              if (i === 0) this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1));
130              if (i === 1) this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1));
131          }
132          const t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
133          if (0 < t2 && t2 < 1) {
134              if (i === 0) this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2));
135              if (i === 1) this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2));
136          }
137      }
138  };
139  
140  /**
141   * Add a quadratic curve to the bounding box.
142   * This extends the bounding box to include the entire quadratic curve.
143   * @param {number} x0 - The starting X coordinate.
144   * @param {number} y0 - The starting Y coordinate.
145   * @param {number} x1 - The X coordinate of the control point.
146   * @param {number} y1 - The Y coordinate of the control point.
147   * @param {number} x - The ending X coordinate.
148   * @param {number} y - The ending Y coordinate.
149   */
150  BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) {
151      const cp1x = x0 + 2 / 3 * (x1 - x0);
152      const cp1y = y0 + 2 / 3 * (y1 - y0);
153      const cp2x = cp1x + 1 / 3 * (x - x0);
154      const cp2y = cp1y + 1 / 3 * (y - y0);
155      this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y);
156  };
157  
158  export default BoundingBox;