David PHAM-VAN

Automatically calculate Shape() bounding box

@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 - Add support for Icon Fonts (MaterialIcons) 12 - Add support for Icon Fonts (MaterialIcons)
13 - Opt-out from dart library 13 - Opt-out from dart library
14 - Improve graphic operations 14 - Improve graphic operations
  15 +- Automatically calculate Shape() bounding box
15 16
16 ## 1.12.0 17 ## 1.12.0
17 18
@@ -28,6 +28,7 @@ import 'graphic_state.dart'; @@ -28,6 +28,7 @@ import 'graphic_state.dart';
28 import 'graphic_stream.dart'; 28 import 'graphic_stream.dart';
29 import 'image.dart'; 29 import 'image.dart';
30 import 'page.dart'; 30 import 'page.dart';
  31 +import 'rect.dart';
31 import 'shading.dart'; 32 import 'shading.dart';
32 import 'stream.dart'; 33 import 'stream.dart';
33 34
@@ -528,6 +529,13 @@ class PdfGraphics { @@ -528,6 +529,13 @@ class PdfGraphics {
528 writeSvgPathDataToPath(d, proxy); 529 writeSvgPathDataToPath(d, proxy);
529 } 530 }
530 531
  532 + /// Calculates the bounding box of an SVG path
  533 + static PdfRect shapeBoundingBox(String d) {
  534 + final proxy = _PathBBProxy();
  535 + writeSvgPathDataToPath(d, proxy);
  536 + return proxy.box;
  537 + }
  538 +
531 /// Set line starting and ending cap type 539 /// Set line starting and ending cap type
532 void setLineCap(PdfLineCap cap) { 540 void setLineCap(PdfLineCap cap) {
533 buf.putString('${cap.index} J\n'); 541 buf.putString('${cap.index} J\n');
@@ -586,3 +594,112 @@ class _PathProxy extends PathProxy { @@ -586,3 +594,112 @@ class _PathProxy extends PathProxy {
586 canvas.moveTo(x, y); 594 canvas.moveTo(x, y);
587 } 595 }
588 } 596 }
  597 +
  598 +class _PathBBProxy extends PathProxy {
  599 + _PathBBProxy();
  600 +
  601 + var _xMin = double.infinity;
  602 + var _yMin = double.infinity;
  603 + var _xMax = double.negativeInfinity;
  604 + var _yMax = double.negativeInfinity;
  605 +
  606 + var _pX = 0.0;
  607 + var _pY = 0.0;
  608 +
  609 + PdfRect get box {
  610 + if (_xMin > _xMax || _yMin > _yMax) {
  611 + return PdfRect.zero;
  612 + }
  613 + return PdfRect.fromLTRB(_xMin, _yMin, _xMax, _yMax);
  614 + }
  615 +
  616 + @override
  617 + void close() {}
  618 +
  619 + @override
  620 + void cubicTo(
  621 + double x1, double y1, double x2, double y2, double x3, double y3) {
  622 + final tvalues = <double>[];
  623 + double a, b, c, t, t1, t2, b2ac, sqrtb2ac;
  624 +
  625 + for (var i = 0; i < 2; ++i) {
  626 + if (i == 0) {
  627 + b = 6 * _pX - 12 * x1 + 6 * x2;
  628 + a = -3 * _pX + 9 * x1 - 9 * x2 + 3 * x3;
  629 + c = 3 * x1 - 3 * _pX;
  630 + } else {
  631 + b = 6 * _pY - 12 * y1 + 6 * y2;
  632 + a = -3 * _pY + 9 * y1 - 9 * y2 + 3 * y3;
  633 + c = 3 * y1 - 3 * _pY;
  634 + }
  635 + if (a.abs() < 1e-12) {
  636 + if (b.abs() < 1e-12) {
  637 + continue;
  638 + }
  639 + t = -c / b;
  640 + if (0 < t && t < 1) {
  641 + tvalues.add(t);
  642 + }
  643 + continue;
  644 + }
  645 + b2ac = b * b - 4 * c * a;
  646 + if (b2ac < 0) {
  647 + if (b2ac.abs() < 1e-12) {
  648 + t = -b / (2 * a);
  649 + if (0 < t && t < 1) {
  650 + tvalues.add(t);
  651 + }
  652 + }
  653 + continue;
  654 + }
  655 + sqrtb2ac = math.sqrt(b2ac);
  656 + t1 = (-b + sqrtb2ac) / (2 * a);
  657 + if (0 < t1 && t1 < 1) {
  658 + tvalues.add(t1);
  659 + }
  660 + t2 = (-b - sqrtb2ac) / (2 * a);
  661 + if (0 < t2 && t2 < 1) {
  662 + tvalues.add(t2);
  663 + }
  664 + }
  665 +
  666 + for (final t in tvalues) {
  667 + final mt = 1 - t;
  668 + _updateMinMax(
  669 + (mt * mt * mt * _pX) +
  670 + (3 * mt * mt * t * x1) +
  671 + (3 * mt * t * t * x2) +
  672 + (t * t * t * x3),
  673 + (mt * mt * mt * _pY) +
  674 + (3 * mt * mt * t * y1) +
  675 + (3 * mt * t * t * y2) +
  676 + (t * t * t * y3));
  677 + }
  678 + _updateMinMax(_pX, _pY);
  679 + _updateMinMax(x3, y3);
  680 +
  681 + _pX = x3;
  682 + _pY = y3;
  683 + }
  684 +
  685 + @override
  686 + void lineTo(double x, double y) {
  687 + _pX = x;
  688 + _pY = y;
  689 + _updateMinMax(x, y);
  690 + }
  691 +
  692 + @override
  693 + void moveTo(double x, double y) {
  694 + _pX = x;
  695 + _pY = y;
  696 + _updateMinMax(x, y);
  697 + }
  698 +
  699 + void _updateMinMax(double x, double y) {
  700 + _xMin = math.min(_xMin, x);
  701 + _yMin = math.min(_yMin, y);
  702 + _xMax = math.max(_xMax, x);
  703 + _yMax = math.max(_yMax, y);
  704 + }
  705 +}
@@ -154,12 +154,11 @@ class Shape extends Widget { @@ -154,12 +154,11 @@ class Shape extends Widget {
154 this.shape, { 154 this.shape, {
155 this.strokeColor, 155 this.strokeColor,
156 this.fillColor, 156 this.fillColor,
157 - this.width = 1.0,  
158 - this.height = 1.0, 157 + this.width,
  158 + this.height,
159 this.fit = BoxFit.contain, 159 this.fit = BoxFit.contain,
160 - }) : assert(width != null && width > 0.0),  
161 - assert(height != null && height > 0.0),  
162 - aspectRatio = height / width; 160 + }) : assert(width == null || width > 0.0),
  161 + assert(height == null || height > 0.0);
163 162
164 final String shape; 163 final String shape;
165 164
@@ -171,34 +170,49 @@ class Shape extends Widget { @@ -171,34 +170,49 @@ class Shape extends Widget {
171 170
172 final double height; 171 final double height;
173 172
174 - final double aspectRatio;  
175 -  
176 final BoxFit fit; 173 final BoxFit fit;
177 174
  175 + PdfRect _boundingBox;
  176 +
178 @override 177 @override
179 void layout(Context context, BoxConstraints constraints, 178 void layout(Context context, BoxConstraints constraints,
180 {bool parentUsesSize = false}) { 179 {bool parentUsesSize = false}) {
  180 + if (width == null || height == null) {
  181 + // Compute the bounding box
  182 + _boundingBox = PdfGraphics.shapeBoundingBox(shape);
  183 + } else {
  184 + _boundingBox = PdfRect(0, 0, width, height);
  185 + }
  186 +
181 final w = constraints.hasBoundedWidth 187 final w = constraints.hasBoundedWidth
182 ? constraints.maxWidth 188 ? constraints.maxWidth
183 - : constraints.constrainWidth(width); 189 + : constraints.constrainWidth(_boundingBox.width);
184 final h = constraints.hasBoundedHeight 190 final h = constraints.hasBoundedHeight
185 ? constraints.maxHeight 191 ? constraints.maxHeight
186 - : constraints.constrainHeight(height); 192 + : constraints.constrainHeight(_boundingBox.height);
187 193
188 - final sizes = applyBoxFit(fit, PdfPoint(width, height), PdfPoint(w, h));  
189 - box = PdfRect.fromPoints(PdfPoint.zero, sizes.destination); 194 + final sizes = applyBoxFit(fit, _boundingBox.size, PdfPoint(w, h));
  195 + box = PdfRect.fromPoints(
  196 + PdfPoint.zero,
  197 + sizes.destination,
  198 + );
190 } 199 }
191 200
192 @override 201 @override
193 void paint(Context context) { 202 void paint(Context context) {
194 super.paint(context); 203 super.paint(context);
195 204
196 - final mat = Matrix4.identity();  
197 - mat.translate(box.x, box.y + box.height);  
198 - mat.scale(box.width / width, -box.height / height);  
199 context.canvas 205 context.canvas
200 ..saveContext() 206 ..saveContext()
201 - ..setTransform(mat); 207 + ..setTransform(
  208 + Matrix4.identity()
  209 + ..translate(box.x, box.y + box.height)
  210 + ..scale(
  211 + box.width / _boundingBox.width,
  212 + -box.height / _boundingBox.height,
  213 + )
  214 + ..translate(-_boundingBox.x, -_boundingBox.y),
  215 + );
202 216
203 if (fillColor != null) { 217 if (fillColor != null) {
204 context.canvas 218 context.canvas