diff --git a/chapters/aligning/content.en-GB.md b/chapters/aligning/content.en-GB.md index f50fd2e8..0282abb2 100644 --- a/chapters/aligning/content.en-GB.md +++ b/chapters/aligning/content.en-GB.md @@ -41,4 +41,4 @@ If we drop all the zero-terms, this gives us: We can see that our original curve definition has been simplified considerably. The following graphics illustrate the result of aligning our example curves to the x-axis, with the cubic case using the coordinates that were just used in the example formulae: - + diff --git a/chapters/aligning/cubic.js b/chapters/aligning/cubic.js index 4cd2def2..ad00abea 100644 --- a/chapters/aligning/cubic.js +++ b/chapters/aligning/cubic.js @@ -13,27 +13,40 @@ draw() { translate(this.width/2, 0); line(0,0,0,this.height); + this.drawRTCurve( + this.rotatePoints( + this.translatePoints( + this.curve.points + ) + ) + ); +} + +translatePoints(points) { // translate to (0,0) - let points = this.curve.points; let m = points[0]; - points = points.map(v => { + return points.map(v => { return { x: v.x - m.x, y: v.y - m.y } }); +} +rotatePoints(points) { // rotate so that last point is (...,0) let dx = points[3].x; let dy = points[3].y; let a = atan2(dy, dx); - points = points.map(v => { + return points.map(v => { return { x: v.x * cos(-a) - v.y * sin(-a), y: v.x * sin(-a) + v.y * cos(-a) }; }); +} +drawRTCurve(points) { let ncurve = new Bezier(this, points); translate(60, this.height/2); setStroke(`grey`); diff --git a/chapters/aligning/handler.js b/chapters/aligning/handler.js deleted file mode 100644 index cd5f252c..00000000 --- a/chapters/aligning/handler.js +++ /dev/null @@ -1,72 +0,0 @@ -module.exports = { - /** - * Setup function for a default quadratic curve. - */ - setupQuadratic: function(api) { - var curve = api.getDefaultQuadratic(); - api.setCurve(curve); - }, - - /** - * Setup function for a default cubic curve. - */ - setupCubic: function(api) { - var curve = api.getDefaultCubic(); - api.setCurve(curve); - }, - - /** - * A coordinate rotation function that rotates and - * translates the curve, such that the first coordinate - * of the curve is (0,0) and the last coordinate is (..., 0) - */ - align: function(points, line) { - var tx = line.p1.x, - ty = line.p1.y, - // The atan2 function is so important to computing - // that most CPUs have a dedicated implementation - // at the hardware level for it. - a = -Math.atan2(line.p2.y-ty, line.p2.x-tx), - cos = Math.cos, - sin = Math.sin, - d = function(v) { - return { - x: (v.x-tx)*cos(a) - (v.y-ty)*sin(a), - y: (v.x-tx)*sin(a) + (v.y-ty)*cos(a) - }; - }; - return points.map(d); - }, - - /** - * Draw a curve and its aligned counterpart - * side by side across two panels. - */ - draw: function(api, curve) { - api.setPanelCount(2); - api.reset(); - api.drawSkeleton(curve); - api.drawCurve(curve); - - var pts = curve.points; - var line = {p1: pts[0], p2: pts[pts.length-1]}; - var apts = this.align(pts, line); - var aligned = new api.Bezier(apts); - var w = api.getPanelWidth(); - var h = api.getPanelHeight(); - - var offset = {x:w, y:0}; - api.setColor("black"); - api.drawLine({x:0,y:0}, {x:0,y:h}, offset); - offset.x += w/4; - offset.y += h/2; - api.setColor("grey"); - api.drawLine({x:0,y:-h/2}, {x:0,y:h/2}, offset); - api.drawLine({x:-w/4,y:0}, {x:w,y:0}, offset); - api.setFill("grey"); - - api.setColor("black"); - api.drawSkeleton(aligned, offset); - api.drawCurve(aligned, offset); - } -}; diff --git a/chapters/aligning/quadratic.js b/chapters/aligning/quadratic.js index bc653ba3..0b4953fd 100644 --- a/chapters/aligning/quadratic.js +++ b/chapters/aligning/quadratic.js @@ -13,27 +13,40 @@ draw() { translate(this.width/2, 0); line(0,0,0,this.height); + this.drawRTCurve( + this.rotatePoints( + this.translatePoints( + this.curve.points + ) + ) + ); +} + +translatePoints(points) { // translate to (0,0) - let points = this.curve.points; let m = points[0]; - points = points.map(v => { + return points.map(v => { return { x: v.x - m.x, y: v.y - m.y } }); +} +rotatePoints(points) { // rotate so that last point is (...,0) let dx = points[2].x; let dy = points[2].y; let a = atan2(dy, dx); - points = points.map(v => { + return points.map(v => { return { x: v.x * cos(-a) - v.y * sin(-a), y: v.x * sin(-a) + v.y * cos(-a) }; }); +} +drawRTCurve(points) { let ncurve = new Bezier(this, points); translate(10, this.height/2); setStroke(`grey`); diff --git a/chapters/boundingbox/cubic.js b/chapters/boundingbox/cubic.js index 9b5728d8..e67c98a3 100644 --- a/chapters/boundingbox/cubic.js +++ b/chapters/boundingbox/cubic.js @@ -28,7 +28,7 @@ draw() { if (p.x > maxx) maxx = p.x; if (p.y < miny) miny = p.y; if (p.y > maxy) maxy = p.y; - circle(p.x, p.y, 3); + if (t > 0 && t< 1) circle(p.x, p.y, 3); }); setStroke(`#0F0`); diff --git a/chapters/boundingbox/quadratic.js b/chapters/boundingbox/quadratic.js index 7c7d7c6b..3f4e1e84 100644 --- a/chapters/boundingbox/quadratic.js +++ b/chapters/boundingbox/quadratic.js @@ -27,7 +27,7 @@ draw() { if (p.x > maxx) maxx = p.x; if (p.y < miny) miny = p.y; if (p.y > maxy) maxy = p.y; - circle(p.x, p.y, 3); + if (t > 0 && t< 1) circle(p.x, p.y, 3); }); setStroke(`#0F0`); diff --git a/chapters/tightbounds/content.en-GB.md b/chapters/tightbounds/content.en-GB.md index 2f68ff54..be8c8935 100644 --- a/chapters/tightbounds/content.en-GB.md +++ b/chapters/tightbounds/content.en-GB.md @@ -1,8 +1,10 @@ -# Tight boxes +# Tight bounding boxes -With our knowledge of bounding boxes, and curve alignment, We can now form the "tight" bounding box for curves. We first align our curve, recording the translation we performed, "T", and the rotation angle we used, "R". We then determine the aligned curve's normal bounding box. Once we have that, we can map that bounding box back to our original curve by rotating it by -R, and then translating it by -T. We now have nice tight bounding boxes for our curves: +With our knowledge of bounding boxes, and curve alignment, We can now form the "tight" bounding box for curves. We first align our curve, recording the translation we performed, "T", and the rotation angle we used, "R". We then determine the aligned curve's normal bounding box. Once we have that, we can map that bounding box back to our original curve by rotating it by -R, and then translating it by -T. - - +We now have nice tight bounding boxes for our curves: -These are, strictly speaking, not necessarily the tightest possible bounding boxes. It is possible to compute the optimal bounding box by determining which spanning lines we need to effect a minimal box area, but because of the parametric nature of Bézier curves this is actually a rather costly operation, and the gain in bounding precision is often not worth it. If there is high demand for it, I'll add a section on how to precisely compute the best fit bounding box, but the maths is fairly grueling and just not really worth spending time on. + + + +These are, strictly speaking, not necessarily the tightest possible bounding boxes. It is possible to compute the optimal bounding box by determining which spanning lines we need to effect a minimal box area, but because of the parametric nature of Bézier curves this is actually a rather costly operation, and the gain in bounding precision is often not worth it. diff --git a/chapters/tightbounds/cubic.js b/chapters/tightbounds/cubic.js new file mode 100644 index 00000000..65fcae85 --- /dev/null +++ b/chapters/tightbounds/cubic.js @@ -0,0 +1,79 @@ +setup() { + const curve = this.curve = Bezier.defaultCubic(this); + curve.points[2].x = 210; + setMovable(curve.points); +} + +draw() { + const curve = this.curve; + + clear(); + curve.drawSkeleton(); + curve.drawCurve(); + curve.drawPoints(); + + let translated = this.translatePoints(curve.points); + let rotated = this.rotatePoints(translated); + let rtcurve = new Bezier(this, rotated); + let extrema = rtcurve.extrema(); + + let minx = Number.MAX_SAFE_INTEGER, + miny = minx, + maxx = Number.MIN_SAFE_INTEGER, + maxy = maxx; + + setStroke(`red`); + + [0, ...extrema.x, ...extrema.y, 1].forEach(t => { + let p = curve.get(t); + let rtp = rtcurve.get(t); + if (rtp.x < minx) minx = rtp.x; + if (rtp.x > maxx) maxx = rtp.x; + if (rtp.y < miny) miny = rtp.y; + if (rtp.y > maxy) maxy = rtp.y; + if (t > 0 && t< 1) circle(p.x, p.y, 3); + }); + + noFill(); + setStroke(`#0F0`); + + let tx = curve.points[0].x; + let ty = curve.points[0].y; + let a = rotated[0].a; + + start(); + vertex(tx + minx * cos(a) - miny * sin(a), ty + minx * sin(a) + miny * cos(a)); + vertex(tx + maxx * cos(a) - miny * sin(a), ty + maxx * sin(a) + miny * cos(a)); + vertex(tx + maxx * cos(a) - maxy * sin(a), ty + maxx * sin(a) + maxy * cos(a)); + vertex(tx + minx * cos(a) - maxy * sin(a), ty + minx * sin(a) + maxy * cos(a)); + end(true); +} + +translatePoints(points) { + // translate to (0,0) + let m = points[0]; + return points.map(v => { + return { + x: v.x - m.x, + y: v.y - m.y + } + }); +} + +rotatePoints(points) { + // rotate so that last point is (...,0) + let dx = points[3].x; + let dy = points[3].y; + let a = atan2(dy, dx); + return points.map(v => { + return { + a: a, + x: v.x * cos(-a) - v.y * sin(-a), + y: v.x * sin(-a) + v.y * cos(-a) + }; + }); +} + +onMouseMove() { + redraw(); +} diff --git a/chapters/tightbounds/handler.js b/chapters/tightbounds/handler.js deleted file mode 100644 index 7907ea84..00000000 --- a/chapters/tightbounds/handler.js +++ /dev/null @@ -1,71 +0,0 @@ -module.exports = { - setupQuadratic: function(api) { - var curve = api.getDefaultQuadratic(); - api.setCurve(curve); - }, - - setupCubic: function(api) { - var curve = api.getDefaultCubic(); - api.setCurve(curve); - }, - - align: function(points, line) { - var tx = line.p1.x, - ty = line.p1.y, - a = -Math.atan2(line.p2.y-ty, line.p2.x-tx), - cos = Math.cos, - sin = Math.sin, - d = function(v) { - return { - x: (v.x-tx)*cos(a) - (v.y-ty)*sin(a), - y: (v.x-tx)*sin(a) + (v.y-ty)*cos(a), - a: a - }; - }; - return points.map(d); - }, - - // FIXME: I'm not satisfied with needing to turn a bbox[] into a point[], - // this needs a bezier.js solution, really, with a call curve.tightbbox() - transpose: function(points, angle, offset) { - var tx = offset.x, - ty = offset.y, - cos = Math.cos, - sin = Math.sin, - v = [points.x.min, points.y.min, points.x.max, points.y.max]; - return [ - {x: v[0], y: v[1] }, - {x: v[2], y: v[1] }, - {x: v[2], y: v[3] }, - {x: v[0], y: v[3] } - ].map(p => { - var x=p.x, y=p.y; - return { - x: x*cos(angle) - y*sin(angle) + tx, - y: x*sin(angle) + y*cos(angle) + ty - }; - }); - }, - - draw: function(api, curve) { - api.reset(); - - var pts = curve.points; - var line = {p1: pts[0], p2: pts[pts.length-1]}; - var apts = this.align(pts, line); - var angle = -apts[0].a; - var aligned = new api.Bezier(apts); - var bbox = aligned.bbox(); - var tpts = this.transpose(bbox, angle, pts[0]); - - api.setColor("#00FF00"); - api.drawLine(tpts[0], tpts[1]); - api.drawLine(tpts[1], tpts[2]); - api.drawLine(tpts[2], tpts[3]); - api.drawLine(tpts[3], tpts[0]); - - api.setColor("black"); - api.drawSkeleton(curve); - api.drawCurve(curve); - } -}; diff --git a/chapters/tightbounds/quadratic.js b/chapters/tightbounds/quadratic.js new file mode 100644 index 00000000..31137b81 --- /dev/null +++ b/chapters/tightbounds/quadratic.js @@ -0,0 +1,78 @@ +setup() { + this.curve = Bezier.defaultQuadratic(this); + setMovable(this.curve.points); +} + +draw() { + const curve = this.curve; + + clear(); + curve.drawSkeleton(); + curve.drawCurve(); + curve.drawPoints(); + + let translated = this.translatePoints(curve.points); + let rotated = this.rotatePoints(translated); + let rtcurve = new Bezier(this, rotated); + let extrema = rtcurve.extrema(); + + let minx = Number.MAX_SAFE_INTEGER, + miny = minx, + maxx = Number.MIN_SAFE_INTEGER, + maxy = maxx; + + setStroke(`red`); + + [0, ...extrema.x, ...extrema.y, 1].forEach(t => { + let p = curve.get(t); + let rtp = rtcurve.get(t); + if (rtp.x < minx) minx = rtp.x; + if (rtp.x > maxx) maxx = rtp.x; + if (rtp.y < miny) miny = rtp.y; + if (rtp.y > maxy) maxy = rtp.y; + if (t > 0 && t< 1) circle(p.x, p.y, 3); + }); + + noFill(); + setStroke(`#0F0`); + + let tx = curve.points[0].x; + let ty = curve.points[0].y; + let a = rotated[0].a; + + start(); + vertex(tx + minx * cos(a) - miny * sin(a), ty + minx * sin(a) + miny * cos(a)); + vertex(tx + maxx * cos(a) - miny * sin(a), ty + maxx * sin(a) + miny * cos(a)); + vertex(tx + maxx * cos(a) - maxy * sin(a), ty + maxx * sin(a) + maxy * cos(a)); + vertex(tx + minx * cos(a) - maxy * sin(a), ty + minx * sin(a) + maxy * cos(a)); + end(true); +} + +translatePoints(points) { + // translate to (0,0) + let m = points[0]; + return points.map(v => { + return { + x: v.x - m.x, + y: v.y - m.y + } + }); +} + +rotatePoints(points) { + // rotate so that last point is (...,0) + let dx = points[2].x; + let dy = points[2].y; + let a = atan2(dy, dx); + return points.map(v => { + return { + a: a, + x: v.x * cos(-a) - v.y * sin(-a), + y: v.x * sin(-a) + v.y * cos(-a) + }; + }); +} + +onMouseMove() { + redraw(); +} diff --git a/images/chapters/aligning/b93cd2d19303c6bcd78b130387cd678a.png b/images/chapters/aligning/4da87bfcb036722be99f4b1166d5daeb.png similarity index 100% rename from images/chapters/aligning/b93cd2d19303c6bcd78b130387cd678a.png rename to images/chapters/aligning/4da87bfcb036722be99f4b1166d5daeb.png diff --git a/images/chapters/aligning/be2c5ccf3d1136da7f04d39f63e4b94b.png b/images/chapters/aligning/54238957a61113fae905188b9eb1a582.png similarity index 100% rename from images/chapters/aligning/be2c5ccf3d1136da7f04d39f63e4b94b.png rename to images/chapters/aligning/54238957a61113fae905188b9eb1a582.png diff --git a/images/chapters/boundingbox/168229f33086b9919756f4a062ff00bd.png b/images/chapters/boundingbox/168229f33086b9919756f4a062ff00bd.png new file mode 100644 index 00000000..023fec5e Binary files /dev/null and b/images/chapters/boundingbox/168229f33086b9919756f4a062ff00bd.png differ diff --git a/images/chapters/boundingbox/1f0e2a574995607c61adf48eded66458.png b/images/chapters/boundingbox/1f0e2a574995607c61adf48eded66458.png new file mode 100644 index 00000000..ba413e3a Binary files /dev/null and b/images/chapters/boundingbox/1f0e2a574995607c61adf48eded66458.png differ diff --git a/images/chapters/boundingbox/7d6e04cb038bc7d8e5cfa28beee46ae5.png b/images/chapters/boundingbox/7d6e04cb038bc7d8e5cfa28beee46ae5.png deleted file mode 100644 index acd205e6..00000000 Binary files a/images/chapters/boundingbox/7d6e04cb038bc7d8e5cfa28beee46ae5.png and /dev/null differ diff --git a/images/chapters/boundingbox/a5595ac771a586fee13b0bf1fd2f4e49.png b/images/chapters/boundingbox/a5595ac771a586fee13b0bf1fd2f4e49.png deleted file mode 100644 index d97508ae..00000000 Binary files a/images/chapters/boundingbox/a5595ac771a586fee13b0bf1fd2f4e49.png and /dev/null differ diff --git a/images/chapters/tightbounds/40061dae02a2f78fdaaedd37289dc38a.png b/images/chapters/tightbounds/40061dae02a2f78fdaaedd37289dc38a.png new file mode 100644 index 00000000..f5dd67d2 Binary files /dev/null and b/images/chapters/tightbounds/40061dae02a2f78fdaaedd37289dc38a.png differ diff --git a/images/chapters/tightbounds/95d28739a00d2c9c0c4bbc3c93ce3e05.png b/images/chapters/tightbounds/95d28739a00d2c9c0c4bbc3c93ce3e05.png new file mode 100644 index 00000000..b8d774c4 Binary files /dev/null and b/images/chapters/tightbounds/95d28739a00d2c9c0c4bbc3c93ce3e05.png differ diff --git a/index.html b/index.html index 93b90df0..5ee3fedf 100644 --- a/index.html +++ b/index.html @@ -111,7 +111,7 @@
  • Finding extremities: root finding
  • Bounding boxes
  • Aligning curves
  • -
  • Tight boxes
  • +
  • Tight bounding boxes
  • Curve inflections
  • Canonical form (for cubic curves)
  • Finding Y, given X
  • @@ -3356,7 +3356,7 @@ function getCubicRoots(pa, pb, pc, pd) { Scripts are disabled. Showing fallback image. @@ -3372,7 +3372,7 @@ function getCubicRoots(pa, pb, pc, pd) { Scripts are disabled. Showing fallback image. @@ -3457,7 +3457,7 @@ function getCubicRoots(pa, pb, pc, pd) { Scripts are disabled. Showing fallback image. @@ -3473,7 +3473,7 @@ function getCubicRoots(pa, pb, pc, pd) { Scripts are disabled. Showing fallback image. @@ -3481,7 +3481,7 @@ function getCubicRoots(pa, pb, pc, pd) { >
    -

    Tight boxes

    +

    Tight bounding boxes

    With our knowledge of bounding boxes, and curve alignment, We can now form the "tight" bounding box for curves. We first align our @@ -3489,18 +3489,41 @@ function getCubicRoots(pa, pb, pc, pd) { angle we used, "R". We then determine the aligned curve's normal bounding box. Once we have that, we can map that bounding box back to our original curve by rotating it by -R, and then translating it - by -T. We now have nice tight bounding boxes for our curves: + by -T.

    - We now have nice tight bounding boxes for our curves:

    + - + + + Scripts are disabled. Showing fallback image. + + + width="275" + height="275" + src="./chapters/tightbounds/cubic.js" + > + + + Scripts are disabled. Showing fallback image. +

    These are, strictly speaking, not necessarily the tightest possible @@ -3508,10 +3531,7 @@ function getCubicRoots(pa, pb, pc, pd) { by determining which spanning lines we need to effect a minimal box area, but because of the parametric nature of Bézier curves this is actually a rather costly operation, and the gain in bounding - precision is often not worth it. If there is high demand for it, - I'll add a section on how to precisely compute the best fit bounding - box, but the maths is fairly grueling and just not really worth - spending time on. + precision is often not worth it.

    diff --git a/ja-JP/index.html b/ja-JP/index.html index c7e838a9..89828154 100644 --- a/ja-JP/index.html +++ b/ja-JP/index.html @@ -143,7 +143,9 @@
  • Bounding boxes
  • Aligning curves
  • -
  • Tight boxes
  • +
  • + Tight bounding boxes +
  • Curve inflections
  • Scripts are disabled. Showing fallback image. @@ -3042,7 +3044,7 @@ function getCubicRoots(pa, pb, pc, pd) { Scripts are disabled. Showing fallback image. @@ -3127,7 +3129,7 @@ function getCubicRoots(pa, pb, pc, pd) { Scripts are disabled. Showing fallback image. @@ -3143,7 +3145,7 @@ function getCubicRoots(pa, pb, pc, pd) { Scripts are disabled. Showing fallback image. @@ -3151,7 +3153,9 @@ function getCubicRoots(pa, pb, pc, pd) { >
  • -

    Tight boxes

    +

    + Tight bounding boxes +

    With our knowledge of bounding boxes, and curve alignment, We can now form the "tight" bounding box for curves. We first align our @@ -3159,18 +3163,41 @@ function getCubicRoots(pa, pb, pc, pd) { angle we used, "R". We then determine the aligned curve's normal bounding box. Once we have that, we can map that bounding box back to our original curve by rotating it by -R, and then translating it - by -T. We now have nice tight bounding boxes for our curves: + by -T.

    - We now have nice tight bounding boxes for our curves:

    + - + + + Scripts are disabled. Showing fallback image. + + + width="275" + height="275" + src="./chapters/tightbounds/cubic.js" + > + + + Scripts are disabled. Showing fallback image. +

    These are, strictly speaking, not necessarily the tightest possible @@ -3178,10 +3205,7 @@ function getCubicRoots(pa, pb, pc, pd) { by determining which spanning lines we need to effect a minimal box area, but because of the parametric nature of Bézier curves this is actually a rather costly operation, and the gain in bounding - precision is often not worth it. If there is high demand for it, - I'll add a section on how to precisely compute the best fit bounding - box, but the maths is fairly grueling and just not really worth - spending time on. + precision is often not worth it.

    diff --git a/lib/custom-element/api/graphics-api.js b/lib/custom-element/api/graphics-api.js index 9cc52510..b48a8603 100644 --- a/lib/custom-element/api/graphics-api.js +++ b/lib/custom-element/api/graphics-api.js @@ -386,13 +386,14 @@ class GraphicsAPI extends BaseAPI { /** * A signal to draw the current complex shape */ - end() { + end(close = false) { this.ctx.beginPath(); let { x, y } = this.currentShape.first; this.ctx.moveTo(x, y); this.currentShape.segments.forEach((s) => this[`draw${s.type}`](s.points, s.factor) ); + if (close) this.ctx.closePath(); this.ctx.fill(); this.ctx.stroke(); } diff --git a/zh-CN/index.html b/zh-CN/index.html index 69029399..cae8202f 100644 --- a/zh-CN/index.html +++ b/zh-CN/index.html @@ -137,7 +137,9 @@
  • Bounding boxes
  • Aligning curves
  • -
  • Tight boxes
  • +
  • + Tight bounding boxes +
  • Curve inflections
  • Scripts are disabled. Showing fallback image. @@ -3052,7 +3054,7 @@ function getCubicRoots(pa, pb, pc, pd) { Scripts are disabled. Showing fallback image. @@ -3137,7 +3139,7 @@ function getCubicRoots(pa, pb, pc, pd) { Scripts are disabled. Showing fallback image. @@ -3153,7 +3155,7 @@ function getCubicRoots(pa, pb, pc, pd) { Scripts are disabled. Showing fallback image. @@ -3161,7 +3163,9 @@ function getCubicRoots(pa, pb, pc, pd) { >
  • -

    Tight boxes

    +

    + Tight bounding boxes +

    With our knowledge of bounding boxes, and curve alignment, We can now form the "tight" bounding box for curves. We first align our @@ -3169,18 +3173,41 @@ function getCubicRoots(pa, pb, pc, pd) { angle we used, "R". We then determine the aligned curve's normal bounding box. Once we have that, we can map that bounding box back to our original curve by rotating it by -R, and then translating it - by -T. We now have nice tight bounding boxes for our curves: + by -T.

    - We now have nice tight bounding boxes for our curves:

    + - + + + Scripts are disabled. Showing fallback image. + + + width="275" + height="275" + src="./chapters/tightbounds/cubic.js" + > + + + Scripts are disabled. Showing fallback image. +

    These are, strictly speaking, not necessarily the tightest possible @@ -3188,10 +3215,7 @@ function getCubicRoots(pa, pb, pc, pd) { by determining which spanning lines we need to effect a minimal box area, but because of the parametric nature of Bézier curves this is actually a rather costly operation, and the gain in bounding - precision is often not worth it. If there is high demand for it, - I'll add a section on how to precisely compute the best fit bounding - box, but the maths is fairly grueling and just not really worth - spending time on. + precision is often not worth it.