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;