• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5part of ui;
6
7/// Defines how a list of points is interpreted when drawing a set of points.
8///
9/// Used by [Canvas.drawPoints].
10enum PointMode {
11  /// Draw each point separately.
12  ///
13  /// If the [Paint.strokeCap] is [StrokeCap.round], then each point is drawn
14  /// as a circle with the diameter of the [Paint.strokeWidth], filled as
15  /// described by the [Paint] (ignoring [Paint.style]).
16  ///
17  /// Otherwise, each point is drawn as an axis-aligned square with sides of
18  /// length [Paint.strokeWidth], filled as described by the [Paint] (ignoring
19  /// [Paint.style]).
20  points,
21
22  /// Draw each sequence of two points as a line segment.
23  ///
24  /// If the number of points is odd, then the last point is ignored.
25  ///
26  /// The lines are stroked as described by the [Paint] (ignoring
27  /// [Paint.style]).
28  lines,
29
30  /// Draw the entire sequence of point as one line.
31  ///
32  /// The lines are stroked as described by the [Paint] (ignoring
33  /// [Paint.style]).
34  polygon,
35}
36
37/// Defines how a new clip region should be merged with the existing clip
38/// region.
39///
40/// Used by [Canvas.clipRect].
41enum ClipOp {
42  /// Subtract the new region from the existing region.
43  difference,
44
45  /// Intersect the new region from the existing region.
46  intersect,
47}
48
49enum VertexMode {
50  /// Draw each sequence of three points as the vertices of a triangle.
51  triangles,
52
53  /// Draw each sliding window of three points as the vertices of a triangle.
54  triangleStrip,
55
56  /// Draw the first point and each sliding window of two points as the vertices of a triangle.
57  triangleFan,
58}
59
60/// A set of vertex data used by [Canvas.drawVertices].
61class Vertices {
62  Vertices(
63    VertexMode mode,
64    List<Offset> positions, {
65    List<Offset> textureCoordinates,
66    List<Color> colors,
67    List<int> indices,
68  })  : assert(mode != null),
69        assert(positions != null);
70
71  Vertices.raw(
72    VertexMode mode,
73    Float32List positions, {
74    Float32List textureCoordinates,
75    Int32List colors,
76    Uint16List indices,
77  })  : assert(mode != null),
78        assert(positions != null);
79}
80
81/// Records a [Picture] containing a sequence of graphical operations.
82///
83/// To begin recording, construct a [Canvas] to record the commands.
84/// To end recording, use the [PictureRecorder.endRecording] method.
85class PictureRecorder {
86  /// Creates a new idle PictureRecorder. To associate it with a
87  /// [Canvas] and begin recording, pass this [PictureRecorder] to the
88  /// [Canvas] constructor.
89  factory PictureRecorder() {
90    if (engine.experimentalUseSkia) {
91      return engine.SkPictureRecorder();
92    } else {
93      return PictureRecorder._();
94    }
95  }
96
97  PictureRecorder._();
98
99  engine.RecordingCanvas _canvas;
100  Rect cullRect;
101  bool _isRecording = false;
102
103  engine.RecordingCanvas beginRecording(Rect bounds) {
104    assert(!_isRecording);
105    cullRect = bounds;
106    _isRecording = true;
107    _canvas = engine.RecordingCanvas(cullRect);
108    return _canvas;
109  }
110
111  /// Whether this object is currently recording commands.
112  ///
113  /// Specifically, this returns true if a [Canvas] object has been
114  /// created to record commands and recording has not yet ended via a
115  /// call to [endRecording], and false if either this
116  /// [PictureRecorder] has not yet been associated with a [Canvas],
117  /// or the [endRecording] method has already been called.
118  bool get isRecording => _isRecording;
119
120  /// Finishes recording graphical operations.
121  ///
122  /// Returns a picture containing the graphical operations that have been
123  /// recorded thus far. After calling this function, both the picture recorder
124  /// and the canvas objects are invalid and cannot be used further.
125  ///
126  /// Returns null if the PictureRecorder is not associated with a canvas.
127  Picture endRecording() {
128    // Returning null is what the flutter engine does:
129    // lib/ui/painting/picture_recorder.cc
130    if (!_isRecording) {
131      return null;
132    }
133    _isRecording = false;
134    return Picture._(_canvas, cullRect);
135  }
136}
137
138/// An interface for recording graphical operations.
139///
140/// [Canvas] objects are used in creating [Picture] objects, which can
141/// themselves be used with a [SceneBuilder] to build a [Scene]. In
142/// normal usage, however, this is all handled by the framework.
143///
144/// A canvas has a current transformation matrix which is applied to all
145/// operations. Initially, the transformation matrix is the identity transform.
146/// It can be modified using the [translate], [scale], [rotate], [skew],
147/// and [transform] methods.
148///
149/// A canvas also has a current clip region which is applied to all operations.
150/// Initially, the clip region is infinite. It can be modified using the
151/// [clipRect], [clipRRect], and [clipPath] methods.
152///
153/// The current transform and clip can be saved and restored using the stack
154/// managed by the [save], [saveLayer], and [restore] methods.
155class Canvas {
156  engine.RecordingCanvas _canvas;
157
158  /// Creates a canvas for recording graphical operations into the
159  /// given picture recorder.
160  ///
161  /// Graphical operations that affect pixels entirely outside the given
162  /// `cullRect` might be discarded by the implementation. However, the
163  /// implementation might draw outside these bounds if, for example, a command
164  /// draws partially inside and outside the `cullRect`. To ensure that pixels
165  /// outside a given region are discarded, consider using a [clipRect]. The
166  /// `cullRect` is optional; by default, all operations are kept.
167  ///
168  /// To end the recording, call [PictureRecorder.endRecording] on the
169  /// given recorder.
170  Canvas(PictureRecorder recorder, [Rect cullRect]) : assert(recorder != null) {
171    if (recorder.isRecording) {
172      throw ArgumentError(
173          '"recorder" must not already be associated with another Canvas.');
174    }
175    cullRect ??= Rect.largest;
176    _canvas = recorder.beginRecording(cullRect);
177  }
178
179  /// Saves a copy of the current transform and clip on the save stack.
180  ///
181  /// Call [restore] to pop the save stack.
182  ///
183  /// See also:
184  ///
185  ///  * [saveLayer], which does the same thing but additionally also groups the
186  ///    commands done until the matching [restore].
187  void save() {
188    _canvas.save();
189  }
190
191  /// Saves a copy of the current transform and clip on the save stack, and then
192  /// creates a new group which subsequent calls will become a part of. When the
193  /// save stack is later popped, the group will be flattened into a layer and
194  /// have the given `paint`'s [Paint.colorFilter] and [Paint.blendMode]
195  /// applied.
196  ///
197  /// This lets you create composite effects, for example making a group of
198  /// drawing commands semi-transparent. Without using [saveLayer], each part of
199  /// the group would be painted individually, so where they overlap would be
200  /// darker than where they do not. By using [saveLayer] to group them
201  /// together, they can be drawn with an opaque color at first, and then the
202  /// entire group can be made transparent using the [saveLayer]'s paint.
203  ///
204  /// Call [restore] to pop the save stack and apply the paint to the group.
205  ///
206  /// ## Using saveLayer with clips
207  ///
208  /// When a rectangular clip operation (from [clipRect]) is not axis-aligned
209  /// with the raster buffer, or when the clip operation is not rectalinear (e.g.
210  /// because it is a rounded rectangle clip created by [clipRRect] or an
211  /// arbitrarily complicated path clip created by [clipPath]), the edge of the
212  /// clip needs to be anti-aliased.
213  ///
214  /// If two draw calls overlap at the edge of such a clipped region, without
215  /// using [saveLayer], the first drawing will be anti-aliased with the
216  /// background first, and then the second will be anti-aliased with the result
217  /// of blending the first drawing and the background. On the other hand, if
218  /// [saveLayer] is used immediately after establishing the clip, the second
219  /// drawing will cover the first in the layer, and thus the second alone will
220  /// be anti-aliased with the background when the layer is clipped and
221  /// composited (when [restore] is called).
222  ///
223  /// For example, this [CustomPainter.paint] method paints a clean white
224  /// rounded rectangle:
225  ///
226  /// ```dart
227  /// void paint(Canvas canvas, Size size) {
228  ///   Rect rect = Offset.zero & size;
229  ///   canvas.save();
230  ///   canvas.clipRRect(new RRect.fromRectXY(rect, 100.0, 100.0));
231  ///   canvas.saveLayer(rect, new Paint());
232  ///   canvas.drawPaint(new Paint()..color = Colors.red);
233  ///   canvas.drawPaint(new Paint()..color = Colors.white);
234  ///   canvas.restore();
235  ///   canvas.restore();
236  /// }
237  /// ```
238  ///
239  /// On the other hand, this one renders a red outline, the result of the red
240  /// paint being anti-aliased with the background at the clip edge, then the
241  /// white paint being similarly anti-aliased with the background _including
242  /// the clipped red paint_:
243  ///
244  /// ```dart
245  /// void paint(Canvas canvas, Size size) {
246  ///   // (this example renders poorly, prefer the example above)
247  ///   Rect rect = Offset.zero & size;
248  ///   canvas.save();
249  ///   canvas.clipRRect(new RRect.fromRectXY(rect, 100.0, 100.0));
250  ///   canvas.drawPaint(new Paint()..color = Colors.red);
251  ///   canvas.drawPaint(new Paint()..color = Colors.white);
252  ///   canvas.restore();
253  /// }
254  /// ```
255  ///
256  /// This point is moot if the clip only clips one draw operation. For example,
257  /// the following paint method paints a pair of clean white rounded
258  /// rectangles, even though the clips are not done on a separate layer:
259  ///
260  /// ```dart
261  /// void paint(Canvas canvas, Size size) {
262  ///   canvas.save();
263  ///   canvas.clipRRect(new RRect.fromRectXY(Offset.zero & (size / 2.0), 50.0, 50.0));
264  ///   canvas.drawPaint(new Paint()..color = Colors.white);
265  ///   canvas.restore();
266  ///   canvas.save();
267  ///   canvas.clipRRect(new RRect.fromRectXY(size.center(Offset.zero) & (size / 2.0), 50.0, 50.0));
268  ///   canvas.drawPaint(new Paint()..color = Colors.white);
269  ///   canvas.restore();
270  /// }
271  /// ```
272  ///
273  /// (Incidentally, rather than using [clipRRect] and [drawPaint] to draw
274  /// rounded rectangles like this, prefer the [drawRRect] method. These
275  /// examples are using [drawPaint] as a proxy for "complicated draw operations
276  /// that will get clipped", to illustrate the point.)
277  ///
278  /// ## Performance considerations
279  ///
280  /// Generally speaking, [saveLayer] is relatively expensive.
281  ///
282  /// There are a several different hardware architectures for GPUs (graphics
283  /// processing units, the hardware that handles graphics), but most of them
284  /// involve batching commands and reordering them for performance. When layers
285  /// are used, they cause the rendering pipeline to have to switch render
286  /// target (from one layer to another). Render target switches can flush the
287  /// GPU's command buffer, which typically means that optimizations that one
288  /// could get with larger batching are lost. Render target switches also
289  /// generate a lot of memory churn because the GPU needs to copy out the
290  /// current frame buffer contents from the part of memory that's optimized for
291  /// writing, and then needs to copy it back in once the previous render target
292  /// (layer) is restored.
293  ///
294  /// See also:
295  ///
296  ///  * [save], which saves the current state, but does not create a new layer
297  ///    for subsequent commands.
298  ///  * [BlendMode], which discusses the use of [Paint.blendMode] with
299  ///    [saveLayer].
300  void saveLayer(Rect bounds, Paint paint) {
301    assert(paint != null);
302    if (bounds == null) {
303      _saveLayerWithoutBounds(paint);
304    } else {
305      assert(engine.rectIsValid(bounds));
306      _saveLayer(bounds, paint);
307    }
308  }
309
310  void _saveLayerWithoutBounds(Paint paint) {
311    _canvas.saveLayerWithoutBounds(paint);
312  }
313
314  void _saveLayer(Rect bounds, Paint paint) {
315    _canvas.saveLayer(bounds, paint);
316  }
317
318  /// Pops the current save stack, if there is anything to pop.
319  /// Otherwise, does nothing.
320  ///
321  /// Use [save] and [saveLayer] to push state onto the stack.
322  ///
323  /// If the state was pushed with with [saveLayer], then this call will also
324  /// cause the new layer to be composited into the previous layer.
325  void restore() {
326    _canvas.restore();
327  }
328
329  /// Returns the number of items on the save stack, including the
330  /// initial state. This means it returns 1 for a clean canvas, and
331  /// that each call to [save] and [saveLayer] increments it, and that
332  /// each matching call to [restore] decrements it.
333  ///
334  /// This number cannot go below 1.
335  int getSaveCount() => _canvas.saveCount;
336
337  /// Add a translation to the current transform, shifting the coordinate space
338  /// horizontally by the first argument and vertically by the second argument.
339  void translate(double dx, double dy) {
340    _canvas.translate(dx, dy);
341  }
342
343  /// Add an axis-aligned scale to the current transform, scaling by the first
344  /// argument in the horizontal direction and the second in the vertical
345  /// direction.
346  ///
347  /// If [sy] is unspecified, [sx] will be used for the scale in both
348  /// directions.
349  void scale(double sx, [double sy]) => _scale(sx, sy ?? sx);
350
351  void _scale(double sx, double sy) {
352    _canvas.scale(sx, sy);
353  }
354
355  /// Add a rotation to the current transform. The argument is in radians clockwise.
356  void rotate(double radians) {
357    _canvas.rotate(radians);
358  }
359
360  /// Add an axis-aligned skew to the current transform, with the first argument
361  /// being the horizontal skew in radians clockwise around the origin, and the
362  /// second argument being the vertical skew in radians clockwise around the
363  /// origin.
364  void skew(double sx, double sy) {
365    _canvas.skew(sx, sy);
366  }
367
368  /// Multiply the current transform by the specified 4⨉4 transformation matrix
369  /// specified as a list of values in column-major order.
370  void transform(Float64List matrix4) {
371    assert(matrix4 != null);
372    if (matrix4.length != 16) {
373      throw ArgumentError('"matrix4" must have 16 entries.');
374    }
375    _transform(matrix4);
376  }
377
378  void _transform(Float64List matrix4) {
379    _canvas.transform(matrix4);
380  }
381
382  /// Reduces the clip region to the intersection of the current clip and the
383  /// given rectangle.
384  ///
385  /// If [doAntiAlias] is true, then the clip will be anti-aliased.
386  ///
387  /// If multiple draw commands intersect with the clip boundary, this can result
388  /// in incorrect blending at the clip boundary. See [saveLayer] for a
389  /// discussion of how to address that.
390  ///
391  /// Use [ClipOp.difference] to subtract the provided rectangle from the
392  /// current clip.
393  void clipRect(Rect rect,
394      {ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true}) {
395    assert(engine.rectIsValid(rect));
396    assert(clipOp != null);
397    assert(doAntiAlias != null);
398    _clipRect(rect, clipOp, doAntiAlias);
399  }
400
401  void _clipRect(Rect rect, ClipOp clipOp, bool doAntiAlias) {
402    _canvas.clipRect(rect);
403  }
404
405  /// Reduces the clip region to the intersection of the current clip and the
406  /// given rounded rectangle.
407  ///
408  /// If [doAntiAlias] is true, then the clip will be anti-aliased.
409  ///
410  /// If multiple draw commands intersect with the clip boundary, this can result
411  /// in incorrect blending at the clip boundary. See [saveLayer] for a
412  /// discussion of how to address that and some examples of using [clipRRect].
413  void clipRRect(RRect rrect, {bool doAntiAlias = true}) {
414    assert(engine.rrectIsValid(rrect));
415    assert(doAntiAlias != null);
416    _clipRRect(rrect, doAntiAlias);
417  }
418
419  void _clipRRect(RRect rrect, bool doAntiAlias) {
420    _canvas.clipRRect(rrect);
421  }
422
423  /// Reduces the clip region to the intersection of the current clip and the
424  /// given [Path].
425  ///
426  /// If [doAntiAlias] is true, then the clip will be anti-aliased.
427  ///
428  /// If multiple draw commands intersect with the clip boundary, this can result
429  /// multiple draw commands intersect with the clip boundary, this can result
430  /// in incorrect blending at the clip boundary. See [saveLayer] for a
431  /// discussion of how to address that.
432  void clipPath(Path path, {bool doAntiAlias = true}) {
433    assert(path != null); // path is checked on the engine side
434    assert(doAntiAlias != null);
435    _clipPath(path, doAntiAlias);
436  }
437
438  void _clipPath(Path path, bool doAntiAlias) {
439    _canvas.clipPath(path);
440  }
441
442  /// Paints the given [Color] onto the canvas, applying the given
443  /// [BlendMode], with the given color being the source and the background
444  /// being the destination.
445  void drawColor(Color color, BlendMode blendMode) {
446    assert(color != null);
447    assert(blendMode != null);
448    _drawColor(color, blendMode);
449  }
450
451  void _drawColor(Color color, BlendMode blendMode) {
452    _canvas.drawColor(color, blendMode);
453  }
454
455  /// Draws a line between the given points using the given paint. The line is
456  /// stroked, the value of the [Paint.style] is ignored for this call.
457  ///
458  /// The `p1` and `p2` arguments are interpreted as offsets from the origin.
459  void drawLine(Offset p1, Offset p2, Paint paint) {
460    assert(engine.offsetIsValid(p1));
461    assert(engine.offsetIsValid(p2));
462    assert(paint != null);
463    _drawLine(p1, p2, paint);
464  }
465
466  void _drawLine(Offset p1, Offset p2, Paint paint) {
467    _canvas.drawLine(p1, p2, paint);
468  }
469
470  /// Fills the canvas with the given [Paint].
471  ///
472  /// To fill the canvas with a solid color and blend mode, consider
473  /// [drawColor] instead.
474  void drawPaint(Paint paint) {
475    assert(paint != null);
476    _drawPaint(paint);
477  }
478
479  void _drawPaint(Paint paint) {
480    _canvas.drawPaint(paint);
481  }
482
483  /// Draws a rectangle with the given [Paint]. Whether the rectangle is filled
484  /// or stroked (or both) is controlled by [Paint.style].
485  void drawRect(Rect rect, Paint paint) {
486    assert(engine.rectIsValid(rect));
487    assert(paint != null);
488    _drawRect(rect, paint);
489  }
490
491  void _drawRect(Rect rect, Paint paint) {
492    _canvas.drawRect(rect, paint);
493  }
494
495  /// Draws a rounded rectangle with the given [Paint]. Whether the rectangle is
496  /// filled or stroked (or both) is controlled by [Paint.style].
497  void drawRRect(RRect rrect, Paint paint) {
498    assert(engine.rrectIsValid(rrect));
499    assert(paint != null);
500    _drawRRect(rrect, paint);
501  }
502
503  void _drawRRect(RRect rrect, Paint paint) {
504    _canvas.drawRRect(rrect, paint);
505  }
506
507  /// Draws a shape consisting of the difference between two rounded rectangles
508  /// with the given [Paint]. Whether this shape is filled or stroked (or both)
509  /// is controlled by [Paint.style].
510  ///
511  /// This shape is almost but not quite entirely unlike an annulus.
512  void drawDRRect(RRect outer, RRect inner, Paint paint) {
513    assert(engine.rrectIsValid(outer));
514    assert(engine.rrectIsValid(inner));
515    assert(paint != null);
516    _drawDRRect(outer, inner, paint);
517  }
518
519  void _drawDRRect(RRect outer, RRect inner, Paint paint) {
520    _canvas.drawDRRect(outer, inner, paint);
521  }
522
523  /// Draws an axis-aligned oval that fills the given axis-aligned rectangle
524  /// with the given [Paint]. Whether the oval is filled or stroked (or both) is
525  /// controlled by [Paint.style].
526  void drawOval(Rect rect, Paint paint) {
527    assert(engine.rectIsValid(rect));
528    assert(paint != null);
529    _drawOval(rect, paint);
530  }
531
532  void _drawOval(Rect rect, Paint paint) {
533    _canvas.drawOval(rect, paint);
534  }
535
536  /// Draws a circle centered at the point given by the first argument and
537  /// that has the radius given by the second argument, with the [Paint] given in
538  /// the third argument. Whether the circle is filled or stroked (or both) is
539  /// controlled by [Paint.style].
540  void drawCircle(Offset c, double radius, Paint paint) {
541    assert(engine.offsetIsValid(c));
542    assert(paint != null);
543    _drawCircle(c, radius, paint);
544  }
545
546  void _drawCircle(Offset c, double radius, Paint paint) {
547    _canvas.drawCircle(c, radius, paint);
548  }
549
550  /// Draw an arc scaled to fit inside the given rectangle. It starts from
551  /// startAngle radians around the oval up to startAngle + sweepAngle
552  /// radians around the oval, with zero radians being the point on
553  /// the right hand side of the oval that crosses the horizontal line
554  /// that intersects the center of the rectangle and with positive
555  /// angles going clockwise around the oval. If useCenter is true, the arc is
556  /// closed back to the center, forming a circle sector. Otherwise, the arc is
557  /// not closed, forming a circle segment.
558  ///
559  /// This method is optimized for drawing arcs and should be faster than [Path.arcTo].
560  void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter,
561      Paint paint) {
562    assert(engine.rectIsValid(rect));
563    assert(paint != null);
564    const double pi = math.pi;
565    const double pi2 = 2.0 * pi;
566
567    final Path path = Path();
568    if (useCenter) {
569      path.moveTo(
570          (rect.left + rect.right) / 2.0, (rect.top + rect.bottom) / 2.0);
571    }
572    bool forceMoveTo = !useCenter;
573    if (sweepAngle <= -pi2) {
574      path.arcTo(rect, startAngle, -pi, forceMoveTo);
575      startAngle -= pi;
576      path.arcTo(rect, startAngle, -pi, false);
577      startAngle -= pi;
578      forceMoveTo = false;
579      sweepAngle += pi2;
580    }
581    while (sweepAngle >= pi2) {
582      path.arcTo(rect, startAngle, pi, forceMoveTo);
583      startAngle += pi;
584      path.arcTo(rect, startAngle, pi, false);
585      startAngle += pi;
586      forceMoveTo = false;
587      sweepAngle -= pi2;
588    }
589    path.arcTo(rect, startAngle, sweepAngle, forceMoveTo);
590    if (useCenter) {
591      path.close();
592    }
593    _canvas.drawPath(path, paint);
594  }
595
596  /// Draws the given [Path] with the given [Paint]. Whether this shape is
597  /// filled or stroked (or both) is controlled by [Paint.style]. If the path is
598  /// filled, then subpaths within it are implicitly closed (see [Path.close]).
599  void drawPath(Path path, Paint paint) {
600    assert(path != null); // path is checked on the engine side
601    assert(paint != null);
602    _drawPath(path, paint);
603  }
604
605  void _drawPath(Path path, Paint paint) {
606    _canvas.drawPath(path, paint);
607  }
608
609  /// Draws the given [Image] into the canvas with its top-left corner at the
610  /// given [Offset]. The image is composited into the canvas using the given [Paint].
611  void drawImage(Image image, Offset p, Paint paint) {
612    assert(image != null); // image is checked on the engine side
613    assert(engine.offsetIsValid(p));
614    assert(paint != null);
615    _drawImage(image, p, paint);
616  }
617
618  void _drawImage(Image image, Offset p, Paint paint) {
619    _canvas.drawImage(image, p, paint);
620  }
621
622  /// Draws the subset of the given image described by the `src` argument into
623  /// the canvas in the axis-aligned rectangle given by the `dst` argument.
624  ///
625  /// This might sample from outside the `src` rect by up to half the width of
626  /// an applied filter.
627  ///
628  /// Multiple calls to this method with different arguments (from the same
629  /// image) can be batched into a single call to [drawAtlas] to improve
630  /// performance.
631  void drawImageRect(Image image, Rect src, Rect dst, Paint paint) {
632    assert(image != null); // image is checked on the engine side
633    assert(engine.rectIsValid(src));
634    assert(engine.rectIsValid(dst));
635    assert(paint != null);
636    _drawImageRect(image, src, dst, paint);
637  }
638
639  void _drawImageRect(Image image, Rect src, Rect dst, Paint paint) {
640    _canvas.drawImageRect(image, src, dst, paint);
641  }
642
643  /// Draws the given [Image] into the canvas using the given [Paint].
644  ///
645  /// The image is drawn in nine portions described by splitting the image by
646  /// drawing two horizontal lines and two vertical lines, where the `center`
647  /// argument describes the rectangle formed by the four points where these
648  /// four lines intersect each other. (This forms a 3-by-3 grid of regions,
649  /// the center region being described by the `center` argument.)
650  ///
651  /// The four regions in the corners are drawn, without scaling, in the four
652  /// corners of the destination rectangle described by `dst`. The remaining
653  /// five regions are drawn by stretching them to fit such that they exactly
654  /// cover the destination rectangle while maintaining their relative
655  /// positions.
656  void drawImageNine(Image image, Rect center, Rect dst, Paint paint) {
657    assert(image != null); // image is checked on the engine side
658    assert(engine.rectIsValid(center));
659    assert(engine.rectIsValid(dst));
660    assert(paint != null);
661
662    // Assert you can fit the scaled image into dst.
663    assert(image.width - center.width >= dst.width);
664    assert(image.height - center.height >= dst.height);
665
666    // The four unscaled corner rectangles in the from the src.
667    final Rect srcTopLeft = Rect.fromLTWH(
668      0,
669      0,
670      center.left,
671      center.top,
672    );
673    final Rect srcTopRight = Rect.fromLTWH(
674      center.right,
675      0,
676      image.width - center.right,
677      center.top,
678    );
679    final Rect srcBottomLeft = Rect.fromLTWH(
680      0,
681      center.bottom,
682      center.left,
683      image.height - center.bottom,
684    );
685    final Rect srcBottomRight = Rect.fromLTWH(
686      center.right,
687      center.bottom,
688      image.width - center.right,
689      image.height - center.bottom,
690    );
691
692    final Rect dstTopLeft = srcTopLeft.shift(dst.topLeft);
693
694    // The center rectangle in the dst region
695    final Rect dstCenter = Rect.fromLTWH(
696      dstTopLeft.right,
697      dstTopLeft.bottom,
698      dst.width - (srcTopLeft.width + srcTopRight.width),
699      dst.height - (srcTopLeft.height + srcBottomLeft.height),
700    );
701
702    drawImageRect(image, srcTopLeft, dstTopLeft, paint);
703
704    final Rect dstTopRight = Rect.fromLTWH(
705      dstCenter.right,
706      dst.top,
707      srcTopRight.width,
708      srcTopRight.height,
709    );
710    drawImageRect(image, srcTopRight, dstTopRight, paint);
711
712    final Rect dstBottomLeft = Rect.fromLTWH(
713      dst.left,
714      dstCenter.bottom,
715      srcBottomLeft.width,
716      srcBottomLeft.height,
717    );
718    drawImageRect(image, srcBottomLeft, dstBottomLeft, paint);
719
720    final Rect dstBottomRight = Rect.fromLTWH(
721      dstCenter.right,
722      dstCenter.bottom,
723      srcBottomRight.width,
724      srcBottomRight.height,
725    );
726    drawImageRect(image, srcBottomRight, dstBottomRight, paint);
727
728    // Draw the top center rectangle.
729    drawImageRect(
730      image,
731      Rect.fromLTRB(
732        srcTopLeft.right,
733        srcTopLeft.top,
734        srcTopRight.left,
735        srcTopRight.bottom,
736      ),
737      Rect.fromLTRB(
738        dstTopLeft.right,
739        dstTopLeft.top,
740        dstTopRight.left,
741        dstTopRight.bottom,
742      ),
743      paint,
744    );
745
746    // Draw the middle left rectangle.
747    drawImageRect(
748      image,
749      Rect.fromLTRB(
750        srcTopLeft.left,
751        srcTopLeft.bottom,
752        srcBottomLeft.right,
753        srcBottomLeft.top,
754      ),
755      Rect.fromLTRB(
756        dstTopLeft.left,
757        dstTopLeft.bottom,
758        dstBottomLeft.right,
759        dstBottomLeft.top,
760      ),
761      paint,
762    );
763
764    // Draw the center rectangle.
765    drawImageRect(image, center, dstCenter, paint);
766
767    // Draw the middle right rectangle.
768    drawImageRect(
769      image,
770      Rect.fromLTRB(
771        srcTopRight.left,
772        srcTopRight.bottom,
773        srcBottomRight.right,
774        srcBottomRight.top,
775      ),
776      Rect.fromLTRB(
777        dstTopRight.left,
778        dstTopRight.bottom,
779        dstBottomRight.right,
780        dstBottomRight.top,
781      ),
782      paint,
783    );
784
785    // Draw the bottom center rectangle.
786    drawImageRect(
787      image,
788      Rect.fromLTRB(
789        srcBottomLeft.right,
790        srcBottomLeft.top,
791        srcBottomRight.left,
792        srcBottomRight.bottom,
793      ),
794      Rect.fromLTRB(
795        dstBottomLeft.right,
796        dstBottomLeft.top,
797        dstBottomRight.left,
798        dstBottomRight.bottom,
799      ),
800      paint,
801    );
802  }
803
804  /// Draw the given picture onto the canvas. To create a picture, see
805  /// [PictureRecorder].
806  void drawPicture(Picture picture) {
807    assert(picture != null); // picture is checked on the engine side
808    // TODO(het): Support this
809    throw UnimplementedError();
810  }
811
812  /// Draws the text in the given [Paragraph] into this canvas at the given
813  /// [Offset].
814  ///
815  /// The [Paragraph] object must have had [Paragraph.layout] called on it
816  /// first.
817  ///
818  /// To align the text, set the `textAlign` on the [ParagraphStyle] object
819  /// passed to the [new ParagraphBuilder] constructor. For more details see
820  /// [TextAlign] and the discussion at [new ParagraphStyle].
821  ///
822  /// If the text is left aligned or justified, the left margin will be at the
823  /// position specified by the `offset` argument's [Offset.dx] coordinate.
824  ///
825  /// If the text is right aligned or justified, the right margin will be at the
826  /// position described by adding the [ParagraphConstraints.width] given to
827  /// [Paragraph.layout], to the `offset` argument's [Offset.dx] coordinate.
828  ///
829  /// If the text is centered, the centering axis will be at the position
830  /// described by adding half of the [ParagraphConstraints.width] given to
831  /// [Paragraph.layout], to the `offset` argument's [Offset.dx] coordinate.
832  void drawParagraph(Paragraph paragraph, Offset offset) {
833    assert(paragraph != null);
834    assert(engine.offsetIsValid(offset));
835    _drawParagraph(paragraph, offset);
836  }
837
838  void _drawParagraph(Paragraph paragraph, Offset offset) {
839    _canvas.drawParagraph(paragraph, offset);
840  }
841
842  /// Draws a sequence of points according to the given [PointMode].
843  ///
844  /// The `points` argument is interpreted as offsets from the origin.
845  ///
846  /// See also:
847  ///
848  ///  * [drawRawPoints], which takes `points` as a [Float32List] rather than a
849  ///    [List<Offset>].
850  void drawPoints(PointMode pointMode, List<Offset> points, Paint paint) {
851    assert(pointMode != null);
852    assert(points != null);
853    assert(paint != null);
854    throw UnimplementedError();
855  }
856
857  /// Draws a sequence of points according to the given [PointMode].
858  ///
859  /// The `points` argument is interpreted as a list of pairs of floating point
860  /// numbers, where each pair represents an x and y offset from the origin.
861  ///
862  /// See also:
863  ///
864  ///  * [drawPoints], which takes `points` as a [List<Offset>] rather than a
865  ///    [List<Float32List>].
866  void drawRawPoints(PointMode pointMode, Float32List points, Paint paint) {
867    assert(pointMode != null);
868    assert(points != null);
869    assert(paint != null);
870    if (points.length % 2 != 0) {
871      throw ArgumentError('"points" must have an even number of values.');
872    }
873    throw UnimplementedError();
874  }
875
876  void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) {
877    assert(vertices != null); // vertices is checked on the engine side
878    assert(paint != null);
879    assert(blendMode != null);
880    throw UnimplementedError();
881  }
882
883  //
884  // See also:
885  //
886  //  * [drawRawAtlas], which takes its arguments as typed data lists rather
887  //    than objects.
888  void drawAtlas(Image atlas, List<RSTransform> transforms, List<Rect> rects,
889      List<Color> colors, BlendMode blendMode, Rect cullRect, Paint paint) {
890    assert(atlas != null); // atlas is checked on the engine side
891    assert(transforms != null);
892    assert(rects != null);
893    assert(colors != null);
894    assert(blendMode != null);
895    assert(paint != null);
896
897    final int rectCount = rects.length;
898    if (transforms.length != rectCount) {
899      throw ArgumentError('"transforms" and "rects" lengths must match.');
900    }
901    if (colors.isNotEmpty && colors.length != rectCount) {
902      throw ArgumentError(
903          'If non-null, "colors" length must match that of "transforms" and "rects".');
904    }
905
906    // TODO(het): Do we need to support this?
907    throw UnimplementedError();
908  }
909
910  //
911  // The `rstTransforms` argument is interpreted as a list of four-tuples, with
912  // each tuple being ([RSTransform.scos], [RSTransform.ssin],
913  // [RSTransform.tx], [RSTransform.ty]).
914  //
915  // The `rects` argument is interpreted as a list of four-tuples, with each
916  // tuple being ([Rect.left], [Rect.top], [Rect.right], [Rect.bottom]).
917  //
918  // The `colors` argument, which can be null, is interpreted as a list of
919  // 32-bit colors, with the same packing as [Color.value].
920  //
921  // See also:
922  //
923  //  * [drawAtlas], which takes its arguments as objects rather than typed
924  //    data lists.
925  void drawRawAtlas(Image atlas, Float32List rstTransforms, Float32List rects,
926      Int32List colors, BlendMode blendMode, Rect cullRect, Paint paint) {
927    assert(atlas != null); // atlas is checked on the engine side
928    assert(rstTransforms != null);
929    assert(rects != null);
930    assert(colors != null);
931    assert(blendMode != null);
932    assert(paint != null);
933
934    final int rectCount = rects.length;
935    if (rstTransforms.length != rectCount) {
936      throw ArgumentError('"rstTransforms" and "rects" lengths must match.');
937    }
938    if (rectCount % 4 != 0) {
939      throw ArgumentError(
940          '"rstTransforms" and "rects" lengths must be a multiple of four.');
941    }
942    if (colors != null && colors.length * 4 != rectCount) {
943      throw ArgumentError(
944          'If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".');
945    }
946
947    // TODO(het): Do we need to support this?
948    throw UnimplementedError();
949  }
950
951  /// Draws a shadow for a [Path] representing the given material elevation.
952  ///
953  /// The `transparentOccluder` argument should be true if the occluding object
954  /// is not opaque.
955  ///
956  /// The arguments must not be null.
957  void drawShadow(
958      Path path, Color color, double elevation, bool transparentOccluder) {
959    assert(path != null); // path is checked on the engine side
960    assert(color != null);
961    assert(transparentOccluder != null);
962    _canvas.drawShadow(path, color, elevation, transparentOccluder);
963  }
964}
965
966/// An object representing a sequence of recorded graphical operations.
967///
968/// To create a [Picture], use a [PictureRecorder].
969///
970/// A [Picture] can be placed in a [Scene] using a [SceneBuilder], via
971/// the [SceneBuilder.addPicture] method. A [Picture] can also be
972/// drawn into a [Canvas], using the [Canvas.drawPicture] method.
973class Picture {
974  /// This class is created by the engine, and should not be instantiated
975  /// or extended directly.
976  ///
977  /// To create a [Picture], use a [PictureRecorder].
978  Picture._(this.recordingCanvas, this.cullRect);
979
980  /// Creates an image from this picture.
981  ///
982  /// The picture is rasterized using the number of pixels specified by the
983  /// given width and height.
984  ///
985  /// Although the image is returned synchronously, the picture is actually
986  /// rasterized the first time the image is drawn and then cached.
987  Future<Image> toImage(int width, int height) => null;
988
989  /// Release the resources used by this object. The object is no longer usable
990  /// after this method is called.
991  void dispose() {}
992
993  /// Returns the approximate number of bytes allocated for this object.
994  ///
995  /// The actual size of this picture may be larger, particularly if it contains
996  /// references to image or other large objects.
997  int get approximateBytesUsed => 0;
998
999  final engine.RecordingCanvas recordingCanvas;
1000  final Rect cullRect;
1001}
1002
1003/// Determines the winding rule that decides how the interior of a [Path] is
1004/// calculated.
1005///
1006/// This enum is used by the [Path.fillType] property.
1007enum PathFillType {
1008  /// The interior is defined by a non-zero sum of signed edge crossings.
1009  ///
1010  /// For a given point, the point is considered to be on the inside of the path
1011  /// if a line drawn from the point to infinity crosses lines going clockwise
1012  /// around the point a different number of times than it crosses lines going
1013  /// counter-clockwise around that point.
1014  ///
1015  /// See: <https://en.wikipedia.org/wiki/Nonzero-rule>
1016  nonZero,
1017
1018  /// The interior is defined by an odd number of edge crossings.
1019  ///
1020  /// For a given point, the point is considered to be on the inside of the path
1021  /// if a line drawn from the point to infinity crosses an odd number of lines.
1022  ///
1023  /// See: <https://en.wikipedia.org/wiki/Even-odd_rule>
1024  evenOdd,
1025}
1026
1027/// Strategies for combining paths.
1028///
1029/// See also:
1030///
1031/// * [Path.combine], which uses this enum to decide how to combine two paths.
1032// Must be kept in sync with SkPathOp
1033enum PathOperation {
1034  /// Subtract the second path from the first path.
1035  ///
1036  /// For example, if the two paths are overlapping circles of equal diameter
1037  /// but differing centers, the result would be a crescent portion of the
1038  /// first circle that was not overlapped by the second circle.
1039  ///
1040  /// See also:
1041  ///
1042  ///  * [reverseDifference], which is the same but subtracting the first path
1043  ///    from the second.
1044  difference,
1045
1046  /// Create a new path that is the intersection of the two paths, leaving the
1047  /// overlapping pieces of the path.
1048  ///
1049  /// For example, if the two paths are overlapping circles of equal diameter
1050  /// but differing centers, the result would be only the overlapping portion
1051  /// of the two circles.
1052  ///
1053  /// See also:
1054  ///  * [xor], which is the inverse of this operation
1055  intersect,
1056
1057  /// Create a new path that is the union (inclusive-or) of the two paths.
1058  ///
1059  /// For example, if the two paths are overlapping circles of equal diameter
1060  /// but differing centers, the result would be a figure-eight like shape
1061  /// matching the outer boundaries of both circles.
1062  union,
1063
1064  /// Create a new path that is the exclusive-or of the two paths, leaving
1065  /// everything but the overlapping pieces of the path.
1066  ///
1067  /// For example, if the two paths are overlapping circles of equal diameter
1068  /// but differing centers, the figure-eight like shape less the overlapping
1069  /// parts
1070  ///
1071  /// See also:
1072  ///  * [intersect], which is the inverse of this operation
1073  xor,
1074
1075  /// Subtract the first path from the second path.
1076  ///
1077  /// For example, if the two paths are overlapping circles of equal diameter
1078  /// but differing centers, the result would be a crescent portion of the
1079  /// second circle that was not overlapped by the first circle.
1080  ///
1081  /// See also:
1082  ///
1083  ///  * [difference], which is the same but subtracting the second path
1084  ///    from the first.
1085  reverseDifference,
1086}
1087
1088/// A complex, one-dimensional subset of a plane.
1089///
1090/// A path consists of a number of subpaths, and a _current point_.
1091///
1092/// Subpaths consist of segments of various types, such as lines,
1093/// arcs, or beziers. Subpaths can be open or closed, and can
1094/// self-intersect.
1095///
1096/// Closed subpaths enclose a (possibly discontiguous) region of the
1097/// plane based on the current [fillType].
1098///
1099/// The _current point_ is initially at the origin. After each
1100/// operation adding a segment to a subpath, the current point is
1101/// updated to the end of that segment.
1102///
1103/// Paths can be drawn on canvases using [Canvas.drawPath], and can
1104/// used to create clip regions using [Canvas.clipPath].
1105class Path {
1106  final List<engine.Subpath> subpaths;
1107  PathFillType _fillType = PathFillType.nonZero;
1108
1109  engine.Subpath get _currentSubpath => subpaths.isEmpty ? null : subpaths.last;
1110
1111  List<engine.PathCommand> get _commands => _currentSubpath?.commands;
1112
1113  /// The current x-coordinate for this path.
1114  double get _currentX => _currentSubpath?.currentX ?? 0.0;
1115
1116  /// The current y-coordinate for this path.
1117  double get _currentY => _currentSubpath?.currentY ?? 0.0;
1118
1119  /// Recorder used for hit testing paths.
1120  static RawRecordingCanvas _rawRecorder;
1121
1122  /// Create a new empty [Path] object.
1123  factory Path() {
1124    if (engine.experimentalUseSkia) {
1125      return engine.SkPath();
1126    } else {
1127      return Path._();
1128    }
1129  }
1130
1131  Path._() : subpaths = <engine.Subpath>[];
1132
1133  /// Creates a copy of another [Path].
1134  ///
1135  /// This copy is fast and does not require additional memory unless either
1136  /// the `source` path or the path returned by this constructor are modified.
1137  Path.from(Path source)
1138      : subpaths = List<engine.Subpath>.from(source.subpaths);
1139
1140  Path._clone(this.subpaths, this._fillType);
1141
1142  /// Determines how the interior of this path is calculated.
1143  ///
1144  /// Defaults to the non-zero winding rule, [PathFillType.nonZero].
1145  PathFillType get fillType => _fillType;
1146  set fillType(PathFillType value) {
1147    _fillType = value;
1148  }
1149
1150  /// Opens a new subpath with starting point (x, y).
1151  void _openNewSubpath(double x, double y) {
1152    subpaths.add(engine.Subpath(x, y));
1153    _setCurrentPoint(x, y);
1154  }
1155
1156  /// Sets the current point to (x, y).
1157  void _setCurrentPoint(double x, double y) {
1158    _currentSubpath.currentX = x;
1159    _currentSubpath.currentY = y;
1160  }
1161
1162  /// Starts a new subpath at the given coordinate.
1163  void moveTo(double x, double y) {
1164    _openNewSubpath(x, y);
1165    _commands.add(engine.MoveTo(x, y));
1166  }
1167
1168  /// Starts a new subpath at the given offset from the current point.
1169  void relativeMoveTo(double dx, double dy) {
1170    final double newX = _currentX + dx;
1171    final double newY = _currentY + dy;
1172    _openNewSubpath(newX, newY);
1173    _commands.add(engine.MoveTo(newX, newY));
1174  }
1175
1176  /// Adds a straight line segment from the current point to the given
1177  /// point.
1178  void lineTo(double x, double y) {
1179    if (subpaths.isEmpty) {
1180      moveTo(0.0, 0.0);
1181    }
1182    _commands.add(engine.LineTo(x, y));
1183    _setCurrentPoint(x, y);
1184  }
1185
1186  /// Adds a straight line segment from the current point to the point
1187  /// at the given offset from the current point.
1188  void relativeLineTo(double dx, double dy) {
1189    final double newX = _currentX + dx;
1190    final double newY = _currentY + dy;
1191    if (subpaths.isEmpty) {
1192      moveTo(0.0, 0.0);
1193    }
1194    _commands.add(engine.LineTo(newX, newY));
1195    _setCurrentPoint(newX, newY);
1196  }
1197
1198  void _ensurePathStarted() {
1199    if (subpaths.isEmpty) {
1200      subpaths.add(engine.Subpath(0.0, 0.0));
1201    }
1202  }
1203
1204  /// Adds a quadratic bezier segment that curves from the current
1205  /// point to the given point (x2,y2), using the control point
1206  /// (x1,y1).
1207  void quadraticBezierTo(double x1, double y1, double x2, double y2) {
1208    _ensurePathStarted();
1209    _commands.add(engine.QuadraticCurveTo(x1, y1, x2, y2));
1210    _setCurrentPoint(x2, y2);
1211  }
1212
1213  /// Adds a quadratic bezier segment that curves from the current
1214  /// point to the point at the offset (x2,y2) from the current point,
1215  /// using the control point at the offset (x1,y1) from the current
1216  /// point.
1217  void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2) {
1218    _ensurePathStarted();
1219    _commands.add(engine.QuadraticCurveTo(
1220        x1 + _currentX, y1 + _currentY, x2 + _currentX, y2 + _currentY));
1221    _setCurrentPoint(x2 + _currentX, y2 + _currentY);
1222  }
1223
1224  /// Adds a cubic bezier segment that curves from the current point
1225  /// to the given point (x3,y3), using the control points (x1,y1) and
1226  /// (x2,y2).
1227  void cubicTo(
1228      double x1, double y1, double x2, double y2, double x3, double y3) {
1229    _ensurePathStarted();
1230    _commands.add(engine.BezierCurveTo(x1, y1, x2, y2, x3, y3));
1231    _setCurrentPoint(x3, y3);
1232  }
1233
1234  /// Adds a cubic bezier segment that curves from the current point
1235  /// to the point at the offset (x3,y3) from the current point, using
1236  /// the control points at the offsets (x1,y1) and (x2,y2) from the
1237  /// current point.
1238  void relativeCubicTo(
1239      double x1, double y1, double x2, double y2, double x3, double y3) {
1240    _ensurePathStarted();
1241    _commands.add(engine.BezierCurveTo(x1 + _currentX, y1 + _currentY,
1242        x2 + _currentX, y2 + _currentY, x3 + _currentX, y3 + _currentY));
1243    _setCurrentPoint(x3 + _currentX, y3 + _currentY);
1244  }
1245
1246  /// Adds a bezier segment that curves from the current point to the
1247  /// given point (x2,y2), using the control points (x1,y1) and the
1248  /// weight w. If the weight is greater than 1, then the curve is a
1249  /// hyperbola; if the weight equals 1, it's a parabola; and if it is
1250  /// less than 1, it is an ellipse.
1251  void conicTo(double x1, double y1, double x2, double y2, double w) {
1252    final List<Offset> quads =
1253        engine.Conic(_currentX, _currentY, x1, y1, x2, y2, w).toQuads();
1254    final int len = quads.length;
1255    for (int i = 1; i < len; i += 2) {
1256      quadraticBezierTo(
1257          quads[i].dx, quads[i].dy, quads[i + 1].dx, quads[i + 1].dy);
1258    }
1259  }
1260
1261  /// Adds a bezier segment that curves from the current point to the
1262  /// point at the offset (x2,y2) from the current point, using the
1263  /// control point at the offset (x1,y1) from the current point and
1264  /// the weight w. If the weight is greater than 1, then the curve is
1265  /// a hyperbola; if the weight equals 1, it's a parabola; and if it
1266  /// is less than 1, it is an ellipse.
1267  void relativeConicTo(double x1, double y1, double x2, double y2, double w) {
1268    conicTo(_currentX + x1, _currentY + y1, _currentX + x2, _currentY + y2, w);
1269  }
1270
1271  /// If the `forceMoveTo` argument is false, adds a straight line
1272  /// segment and an arc segment.
1273  ///
1274  /// If the `forceMoveTo` argument is true, starts a new subpath
1275  /// consisting of an arc segment.
1276  ///
1277  /// In either case, the arc segment consists of the arc that follows
1278  /// the edge of the oval bounded by the given rectangle, from
1279  /// startAngle radians around the oval up to startAngle + sweepAngle
1280  /// radians around the oval, with zero radians being the point on
1281  /// the right hand side of the oval that crosses the horizontal line
1282  /// that intersects the center of the rectangle and with positive
1283  /// angles going clockwise around the oval.
1284  ///
1285  /// The line segment added if `forceMoveTo` is false starts at the
1286  /// current point and ends at the start of the arc.
1287  void arcTo(
1288      Rect rect, double startAngle, double sweepAngle, bool forceMoveTo) {
1289    assert(engine.rectIsValid(rect));
1290    final Offset center = rect.center;
1291    final double radiusX = rect.width / 2;
1292    final double radiusY = rect.height / 2;
1293    final double startX = radiusX * math.cos(startAngle) + center.dx;
1294    final double startY = radiusY * math.sin(startAngle) + center.dy;
1295    if (forceMoveTo) {
1296      _openNewSubpath(startX, startY);
1297    } else {
1298      lineTo(startX, startY);
1299    }
1300    _commands.add(engine.Ellipse(center.dx, center.dy, radiusX, radiusY, 0.0,
1301        startAngle, startAngle + sweepAngle, sweepAngle.isNegative));
1302
1303    _setCurrentPoint(radiusX * math.cos(startAngle + sweepAngle) + center.dx,
1304        radiusY * math.sin(startAngle + sweepAngle) + center.dy);
1305  }
1306
1307  /// Appends up to four conic curves weighted to describe an oval of `radius`
1308  /// and rotated by `rotation`.
1309  ///
1310  /// The first curve begins from the last point in the path and the last ends
1311  /// at `arcEnd`. The curves follow a path in a direction determined by
1312  /// `clockwise` and `largeArc` in such a way that the sweep angle
1313  /// is always less than 360 degrees.
1314  ///
1315  /// A simple line is appended if either either radii are zero or the last
1316  /// point in the path is `arcEnd`. The radii are scaled to fit the last path
1317  /// point if both are greater than zero but too small to describe an arc.
1318  ///
1319  /// See Conversion from endpoint to center parametrization described in
1320  /// https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1321  /// as reference for implementation.
1322  void arcToPoint(
1323    Offset arcEnd, {
1324    Radius radius = Radius.zero,
1325    double rotation = 0.0,
1326    bool largeArc = false,
1327    bool clockwise = true,
1328  }) {
1329    assert(engine.offsetIsValid(arcEnd));
1330    assert(engine.radiusIsValid(radius));
1331    // _currentX, _currentY are the coordinates of start point on path,
1332    // arcEnd is final point of arc.
1333    // rx,ry are the radii of the eclipse (semi-major/semi-minor axis)
1334    // largeArc is false if arc is spanning less than or equal to 180 degrees.
1335    // clockwise is false if arc sweeps through decreasing angles or true
1336    // if sweeping through increasing angles.
1337    // rotation is the angle from the x-axis of the current coordinate
1338    // system to the x-axis of the eclipse.
1339
1340    double rx = radius.x.abs();
1341    double ry = radius.y.abs();
1342
1343    // If the current point and target point for the arc are identical, it
1344    // should be treated as a zero length path. This ensures continuity in
1345    // animations.
1346    final bool isSamePoint = _currentX == arcEnd.dx && _currentY == arcEnd.dy;
1347
1348    // If rx = 0 or ry = 0 then this arc is treated as a straight line segment
1349    // (a "lineto") joining the endpoints.
1350    // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1351    if (isSamePoint || rx.toInt() == 0 || ry.toInt() == 0) {
1352      _commands.add(engine.LineTo(arcEnd.dx, arcEnd.dy));
1353      _setCurrentPoint(arcEnd.dx, arcEnd.dy);
1354      return;
1355    }
1356
1357    // As an intermediate point to finding center parametrization, place the
1358    // origin on the midpoint between start/end points and rotate to align
1359    // coordinate axis with axes of the ellipse.
1360    final double midPointX = (_currentX - arcEnd.dx) / 2.0;
1361    final double midPointY = (_currentY - arcEnd.dy) / 2.0;
1362
1363    // Convert rotation or radians.
1364    final double xAxisRotation = math.pi * rotation / 180.0;
1365
1366    // Cache cos/sin value.
1367    final double cosXAxisRotation = math.cos(xAxisRotation);
1368    final double sinXAxisRotation = math.sin(xAxisRotation);
1369
1370    // Calculate rotate midpoint as x/yPrime.
1371    final double xPrime =
1372        (cosXAxisRotation * midPointX) + (sinXAxisRotation * midPointY);
1373    final double yPrime =
1374        (-sinXAxisRotation * midPointX) + (cosXAxisRotation * midPointY);
1375
1376    // Check if the radii are big enough to draw the arc, scale radii if not.
1377    // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1378    double rxSquare = rx * rx;
1379    double rySquare = ry * ry;
1380    final double xPrimeSquare = xPrime * xPrime;
1381    final double yPrimeSquare = yPrime * yPrime;
1382
1383    double radiiScale = (xPrimeSquare / rxSquare) + (yPrimeSquare / rySquare);
1384    if (radiiScale > 1) {
1385      radiiScale = math.sqrt(radiiScale);
1386      rx *= radiiScale;
1387      ry *= radiiScale;
1388      rxSquare = rx * rx;
1389      rySquare = ry * ry;
1390    }
1391
1392    // Compute transformed center. eq. 5.2
1393    final double distanceSquare =
1394        (rxSquare * yPrimeSquare) + rySquare * xPrimeSquare;
1395    final double cNumerator = (rxSquare * rySquare) - distanceSquare;
1396    double scaleFactor = math.sqrt(math.max(cNumerator / distanceSquare, 0.0));
1397    if (largeArc == clockwise) {
1398      scaleFactor = -scaleFactor;
1399    }
1400    // Ready to compute transformed center.
1401    final double cxPrime = scaleFactor * ((rx * yPrime) / ry);
1402    final double cyPrime = scaleFactor * (-(ry * xPrime) / rx);
1403
1404    // Rotate to find actual center.
1405    final double cx = cosXAxisRotation * cxPrime -
1406        sinXAxisRotation * cyPrime +
1407        ((_currentX + arcEnd.dx) / 2.0);
1408    final double cy = sinXAxisRotation * cxPrime +
1409        cosXAxisRotation * cyPrime +
1410        ((_currentY + arcEnd.dy) / 2.0);
1411
1412    // Calculate start angle and sweep.
1413    // Start vector is from midpoint of start/end points to transformed center.
1414    final double startVectorX = (xPrime - cxPrime) / rx;
1415    final double startVectorY = (yPrime - cyPrime) / ry;
1416
1417    final double startAngle = math.atan2(startVectorY, startVectorX);
1418    final double endVectorX = (-xPrime - cxPrime) / rx;
1419    final double endVectorY = (-yPrime - cyPrime) / ry;
1420    double sweepAngle = math.atan2(endVectorY, endVectorX) - startAngle;
1421
1422    if (clockwise && sweepAngle < 0) {
1423      sweepAngle += math.pi * 2.0;
1424    } else if (!clockwise && sweepAngle > 0) {
1425      sweepAngle -= math.pi * 2.0;
1426    }
1427
1428    _commands.add(engine.Ellipse(cx, cy, rx, ry, xAxisRotation, startAngle,
1429        startAngle + sweepAngle, sweepAngle.isNegative));
1430
1431    _setCurrentPoint(arcEnd.dx, arcEnd.dy);
1432  }
1433
1434  /// Appends up to four conic curves weighted to describe an oval of `radius`
1435  /// and rotated by `rotation`.
1436  ///
1437  /// The last path point is described by (px, py).
1438  ///
1439  /// The first curve begins from the last point in the path and the last ends
1440  /// at `arcEndDelta.dx + px` and `arcEndDelta.dy + py`. The curves follow a
1441  /// path in a direction determined by `clockwise` and `largeArc`
1442  /// in such a way that the sweep angle is always less than 360 degrees.
1443  ///
1444  /// A simple line is appended if either either radii are zero, or, both
1445  /// `arcEndDelta.dx` and `arcEndDelta.dy` are zero. The radii are scaled to
1446  /// fit the last path point if both are greater than zero but too small to
1447  /// describe an arc.
1448  void relativeArcToPoint(
1449    Offset arcEndDelta, {
1450    Radius radius = Radius.zero,
1451    double rotation = 0.0,
1452    bool largeArc = false,
1453    bool clockwise = true,
1454  }) {
1455    assert(engine.offsetIsValid(arcEndDelta));
1456    assert(engine.radiusIsValid(radius));
1457    arcToPoint(Offset(_currentX + arcEndDelta.dx, _currentY + arcEndDelta.dy),
1458        radius: radius,
1459        rotation: rotation,
1460        largeArc: largeArc,
1461        clockwise: clockwise);
1462  }
1463
1464  /// Adds a new subpath that consists of four lines that outline the
1465  /// given rectangle.
1466  void addRect(Rect rect) {
1467    assert(engine.rectIsValid(rect));
1468    _openNewSubpath(rect.left, rect.top);
1469    _commands
1470        .add(engine.RectCommand(rect.left, rect.top, rect.width, rect.height));
1471  }
1472
1473  /// Adds a new subpath that consists of a curve that forms the
1474  /// ellipse that fills the given rectangle.
1475  ///
1476  /// To add a circle, pass an appropriate rectangle as `oval`.
1477  /// [Rect.fromCircle] can be used to easily describe the circle's center
1478  /// [Offset] and radius.
1479  void addOval(Rect oval) {
1480    assert(engine.rectIsValid(oval));
1481    final Offset center = oval.center;
1482    final double radiusX = oval.width / 2;
1483    final double radiusY = oval.height / 2;
1484
1485    /// At startAngle = 0, the path will begin at center + cos(0) * radius.
1486    _openNewSubpath(center.dx + radiusX, center.dy);
1487    _commands.add(engine.Ellipse(
1488        center.dx, center.dy, radiusX, radiusY, 0.0, 0.0, 2 * math.pi, false));
1489  }
1490
1491  /// Adds a new subpath with one arc segment that consists of the arc
1492  /// that follows the edge of the oval bounded by the given
1493  /// rectangle, from startAngle radians around the oval up to
1494  /// startAngle + sweepAngle radians around the oval, with zero
1495  /// radians being the point on the right hand side of the oval that
1496  /// crosses the horizontal line that intersects the center of the
1497  /// rectangle and with positive angles going clockwise around the
1498  /// oval.
1499  void addArc(Rect oval, double startAngle, double sweepAngle) {
1500    assert(engine.rectIsValid(oval));
1501    final Offset center = oval.center;
1502    final double radiusX = oval.width / 2;
1503    final double radiusY = oval.height / 2;
1504    _openNewSubpath(radiusX * math.cos(startAngle) + center.dx,
1505        radiusY * math.sin(startAngle) + center.dy);
1506    _commands.add(engine.Ellipse(center.dx, center.dy, radiusX, radiusY, 0.0,
1507        startAngle, startAngle + sweepAngle, sweepAngle.isNegative));
1508
1509    _setCurrentPoint(radiusX * math.cos(startAngle + sweepAngle) + center.dx,
1510        radiusY * math.sin(startAngle + sweepAngle) + center.dy);
1511  }
1512
1513  /// Adds a new subpath with a sequence of line segments that connect the given
1514  /// points.
1515  ///
1516  /// If `close` is true, a final line segment will be added that connects the
1517  /// last point to the first point.
1518  ///
1519  /// The `points` argument is interpreted as offsets from the origin.
1520  void addPolygon(List<Offset> points, bool close) {
1521    assert(points != null);
1522    if (points.isEmpty) {
1523      return;
1524    }
1525
1526    moveTo(points.first.dx, points.first.dy);
1527    for (int i = 1; i < points.length; i++) {
1528      final Offset point = points[i];
1529      lineTo(point.dx, point.dy);
1530    }
1531    if (close) {
1532      this.close();
1533    } else {
1534      _setCurrentPoint(points.last.dx, points.last.dy);
1535    }
1536  }
1537
1538  /// Adds a new subpath that consists of the straight lines and
1539  /// curves needed to form the rounded rectangle described by the
1540  /// argument.
1541  void addRRect(RRect rrect) {
1542    assert(engine.rrectIsValid(rrect));
1543
1544    // Set the current point to the top left corner of the rectangle (the
1545    // point on the top of the rectangle farthest to the left that isn't in
1546    // the rounded corner).
1547    // TODO(het): Is this the current point in Flutter?
1548    _openNewSubpath(rrect.tallMiddleRect.left, rrect.top);
1549    _commands.add(engine.RRectCommand(rrect));
1550  }
1551
1552  /// Adds a new subpath that consists of the given `path` offset by the given
1553  /// `offset`.
1554  ///
1555  /// If `matrix4` is specified, the path will be transformed by this matrix
1556  /// after the matrix is translated by the given offset. The matrix is a 4x4
1557  /// matrix stored in column major order.
1558  void addPath(Path path, Offset offset, {Float64List matrix4}) {
1559    assert(path != null); // path is checked on the engine side
1560    assert(engine.offsetIsValid(offset));
1561    if (matrix4 != null) {
1562      assert(engine.matrix4IsValid(matrix4));
1563      _addPathWithMatrix(path, offset.dx, offset.dy, matrix4);
1564    } else {
1565      _addPath(path, offset.dx, offset.dy);
1566    }
1567  }
1568
1569  void _addPath(Path path, double dx, double dy) {
1570    if (dx == 0.0 && dy == 0.0) {
1571      subpaths.addAll(path.subpaths);
1572    } else {
1573      throw UnimplementedError('Cannot add path with non-zero offset');
1574    }
1575  }
1576
1577  void _addPathWithMatrix(Path path, double dx, double dy, Float64List matrix) {
1578    throw UnimplementedError('Cannot add path with transform matrix');
1579  }
1580
1581  /// Adds the given path to this path by extending the current segment of this
1582  /// path with the the first segment of the given path.
1583  ///
1584  /// If `matrix4` is specified, the path will be transformed by this matrix
1585  /// after the matrix is translated by the given `offset`.  The matrix is a 4x4
1586  /// matrix stored in column major order.
1587  void extendWithPath(Path path, Offset offset, {Float64List matrix4}) {
1588    assert(path != null); // path is checked on the engine side
1589    assert(engine.offsetIsValid(offset));
1590    if (matrix4 != null) {
1591      assert(engine.matrix4IsValid(matrix4));
1592      _extendWithPathAndMatrix(path, offset.dx, offset.dy, matrix4);
1593    } else {
1594      _extendWithPath(path, offset.dx, offset.dy);
1595    }
1596  }
1597
1598  void _extendWithPath(Path path, double dx, double dy) {
1599    if (dx == 0.0 && dy == 0.0) {
1600      assert(path.subpaths.length == 1);
1601      _ensurePathStarted();
1602      _commands.addAll(path.subpaths.single.commands);
1603      _setCurrentPoint(
1604          path.subpaths.single.currentX, path.subpaths.single.currentY);
1605    } else {
1606      throw UnimplementedError('Cannot extend path with non-zero offset');
1607    }
1608  }
1609
1610  void _extendWithPathAndMatrix(
1611      Path path, double dx, double dy, Float64List matrix) {
1612    throw UnimplementedError('Cannot extend path with transform matrix');
1613  }
1614
1615  /// Closes the last subpath, as if a straight line had been drawn
1616  /// from the current point to the first point of the subpath.
1617  void close() {
1618    _ensurePathStarted();
1619    _commands.add(const engine.CloseCommand());
1620    _setCurrentPoint(_currentSubpath.startX, _currentSubpath.startY);
1621  }
1622
1623  /// Clears the [Path] object of all subpaths, returning it to the
1624  /// same state it had when it was created. The _current point_ is
1625  /// reset to the origin.
1626  void reset() {
1627    subpaths.clear();
1628  }
1629
1630  /// Tests to see if the given point is within the path. (That is, whether the
1631  /// point would be in the visible portion of the path if the path was used
1632  /// with [Canvas.clipPath].)
1633  ///
1634  /// The `point` argument is interpreted as an offset from the origin.
1635  ///
1636  /// Returns true if the point is in the path, and false otherwise.
1637  ///
1638  /// Note: Not very efficient, it creates a canvas, plays path and calls
1639  /// Context2D isPointInPath. If performance becomes issue, retaining
1640  /// RawRecordingCanvas can remove create/remove rootElement cost.
1641  bool contains(Offset point) {
1642    assert(engine.offsetIsValid(point));
1643    final int subPathCount = subpaths.length;
1644    if (subPathCount == 0) {
1645      return false;
1646    }
1647    final double pointX = point.dx;
1648    final double pointY = point.dy;
1649    if (subPathCount == 1) {
1650      // Optimize for rect/roundrect checks.
1651      final engine.Subpath subPath = subpaths[0];
1652      if (subPath.commands.length == 1) {
1653        final engine.PathCommand cmd = subPath.commands[0];
1654        if (cmd is engine.RectCommand) {
1655          if (pointY < cmd.y || pointY > (cmd.y + cmd.height)) {
1656            return false;
1657          }
1658          if (pointX < cmd.x || pointX > (cmd.x + cmd.width)) {
1659            return false;
1660          }
1661          return true;
1662        } else if (cmd is engine.RRectCommand) {
1663          final RRect rRect = cmd.rrect;
1664          if (pointY < rRect.top || pointY > rRect.bottom) {
1665            return false;
1666          }
1667          if (pointX < rRect.left || pointX > rRect.right) {
1668            return false;
1669          }
1670          if (pointX < (rRect.left + rRect.tlRadiusX) &&
1671              pointY < (rRect.top + rRect.tlRadiusY)) {
1672            // Top left corner
1673            return _ellipseContains(
1674                pointX,
1675                pointY,
1676                rRect.left + rRect.tlRadiusX,
1677                rRect.top + rRect.tlRadiusY,
1678                rRect.tlRadiusX,
1679                rRect.tlRadiusY);
1680          } else if (pointX >= (rRect.right - rRect.trRadiusX) &&
1681              pointY < (rRect.top + rRect.trRadiusY)) {
1682            // Top right corner
1683            return _ellipseContains(
1684                pointX,
1685                pointY,
1686                rRect.right - rRect.trRadiusX,
1687                rRect.top + rRect.trRadiusY,
1688                rRect.trRadiusX,
1689                rRect.trRadiusY);
1690          } else if (pointX >= (rRect.right - rRect.brRadiusX) &&
1691              pointY >= (rRect.bottom - rRect.brRadiusY)) {
1692            // Bottom right corner
1693            return _ellipseContains(
1694                pointX,
1695                pointY,
1696                rRect.right - rRect.brRadiusX,
1697                rRect.bottom - rRect.brRadiusY,
1698                rRect.trRadiusX,
1699                rRect.trRadiusY);
1700          } else if (pointX < (rRect.left + rRect.blRadiusX) &&
1701              pointY >= (rRect.bottom - rRect.blRadiusY)) {
1702            // Bottom left corner
1703            return _ellipseContains(
1704                pointX,
1705                pointY,
1706                rRect.left + rRect.blRadiusX,
1707                rRect.bottom - rRect.blRadiusY,
1708                rRect.trRadiusX,
1709                rRect.trRadiusY);
1710          }
1711          return true;
1712        }
1713      }
1714    }
1715    final Size size = window.physicalSize / window.devicePixelRatio;
1716    _rawRecorder ??= RawRecordingCanvas(size);
1717    // Account for the shift due to padding.
1718    _rawRecorder.translate(-engine.BitmapCanvas.paddingPixels.toDouble(),
1719        -engine.BitmapCanvas.paddingPixels.toDouble());
1720    _rawRecorder.drawPath(
1721        this, (Paint()..color = const Color(0xFF000000)).webOnlyPaintData);
1722    final bool result = _rawRecorder.ctx.isPointInPath(pointX, pointY);
1723    _rawRecorder.dispose();
1724    return result;
1725  }
1726
1727  /// Returns a copy of the path with all the segments of every
1728  /// subpath translated by the given offset.
1729  Path shift(Offset offset) {
1730    assert(engine.offsetIsValid(offset));
1731    final List<engine.Subpath> shiftedSubpaths = <engine.Subpath>[];
1732    for (final engine.Subpath subpath in subpaths) {
1733      shiftedSubpaths.add(subpath.shift(offset));
1734    }
1735    return Path._clone(shiftedSubpaths, fillType);
1736  }
1737
1738  /// Returns a copy of the path with all the segments of every
1739  /// subpath transformed by the given matrix.
1740  Path transform(Float64List matrix4) {
1741    assert(engine.matrix4IsValid(matrix4));
1742    throw UnimplementedError();
1743  }
1744
1745  /// Computes the bounding rectangle for this path.
1746  ///
1747  /// A path containing only axis-aligned points on the same straight line will
1748  /// have no area, and therefore `Rect.isEmpty` will return true for such a
1749  /// path. Consider checking `rect.width + rect.height > 0.0` instead, or
1750  /// using the [computeMetrics] API to check the path length.
1751  ///
1752  /// For many more elaborate paths, the bounds may be inaccurate.  For example,
1753  /// when a path contains a circle, the points used to compute the bounds are
1754  /// the circle's implied control points, which form a square around the
1755  /// circle; if the circle has a transformation applied using [transform] then
1756  /// that square is rotated, and the (axis-aligned, non-rotated) bounding box
1757  /// therefore ends up grossly overestimating the actual area covered by the
1758  /// circle.
1759  // see https://skia.org/user/api/SkPath_Reference#SkPath_getBounds
1760  Rect getBounds() {
1761    // Sufficiently small number for curve eq.
1762    const double epsilon = 0.000000001;
1763    bool ltrbInitialized = false;
1764    double left = 0.0, top = 0.0, right = 0.0, bottom = 0.0;
1765    double curX = 0.0;
1766    double curY = 0.0;
1767    double minX = 0.0, maxX = 0.0, minY = 0.0, maxY = 0.0;
1768    for (engine.Subpath subpath in subpaths) {
1769      for (engine.PathCommand op in subpath.commands) {
1770        bool skipBounds = false;
1771        switch (op.type) {
1772          case engine.PathCommandTypes.moveTo:
1773            final engine.MoveTo cmd = op;
1774            curX = minX = maxX = cmd.x;
1775            curY = minY = maxY = cmd.y;
1776            break;
1777          case engine.PathCommandTypes.lineTo:
1778            final engine.LineTo cmd = op;
1779            curX = minX = maxX = cmd.x;
1780            curY = minY = maxY = cmd.y;
1781            break;
1782          case engine.PathCommandTypes.ellipse:
1783            final engine.Ellipse cmd = op;
1784            // Rotate 4 corners of bounding box.
1785            final double rx = cmd.radiusX;
1786            final double ry = cmd.radiusY;
1787            final double cosVal = math.cos(cmd.rotation);
1788            final double sinVal = math.sin(cmd.rotation);
1789            final double rxCos = rx * cosVal;
1790            final double ryCos = ry * cosVal;
1791            final double rxSin = rx * sinVal;
1792            final double rySin = ry * sinVal;
1793
1794            final double leftDeltaX = rxCos - rySin;
1795            final double rightDeltaX = -rxCos - rySin;
1796            final double topDeltaY = ryCos + rxSin;
1797            final double bottomDeltaY = ryCos - rxSin;
1798
1799            final double centerX = cmd.x;
1800            final double centerY = cmd.y;
1801
1802            double rotatedX = centerX + leftDeltaX;
1803            double rotatedY = centerY + topDeltaY;
1804            minX = maxX = rotatedX;
1805            minY = maxY = rotatedY;
1806
1807            rotatedX = centerX + rightDeltaX;
1808            rotatedY = centerY + bottomDeltaY;
1809            minX = math.min(minX, rotatedX);
1810            maxX = math.max(maxX, rotatedX);
1811            minY = math.min(minY, rotatedY);
1812            maxY = math.max(maxY, rotatedY);
1813
1814            rotatedX = centerX - leftDeltaX;
1815            rotatedY = centerY - topDeltaY;
1816            minX = math.min(minX, rotatedX);
1817            maxX = math.max(maxX, rotatedX);
1818            minY = math.min(minY, rotatedY);
1819            maxY = math.max(maxY, rotatedY);
1820
1821            rotatedX = centerX - rightDeltaX;
1822            rotatedY = centerY - bottomDeltaY;
1823            minX = math.min(minX, rotatedX);
1824            maxX = math.max(maxX, rotatedX);
1825            minY = math.min(minY, rotatedY);
1826            maxY = math.max(maxY, rotatedY);
1827
1828            curX = centerX + cmd.radiusX;
1829            curY = centerY;
1830            break;
1831          case engine.PathCommandTypes.quadraticCurveTo:
1832            final engine.QuadraticCurveTo cmd = op;
1833            final double x1 = curX;
1834            final double y1 = curY;
1835            final double cpX = cmd.x1;
1836            final double cpY = cmd.y1;
1837            final double x2 = cmd.x2;
1838            final double y2 = cmd.y2;
1839
1840            minX = math.min(x1, x2);
1841            minY = math.min(y1, y2);
1842            maxX = math.max(x1, x2);
1843            maxY = math.max(y1, y2);
1844
1845            // Curve equation : (1-t)(1-t)P1 + 2t(1-t)CP + t*t*P2.
1846            // At extrema's derivative = 0.
1847            // Solve for
1848            // -2x1+2tx1 + 2cpX + 4tcpX + 2tx2 = 0
1849            // -2x1 + 2cpX +2t(x1 + 2cpX + x2) = 0
1850            // t = (x1 - cpX) / (x1 - 2cpX + x2)
1851
1852            double denom = x1 - (2 * cpX) + x2;
1853            if (denom.abs() > epsilon) {
1854              final num t1 = (x1 - cpX) / denom;
1855              if ((t1 >= 0) && (t1 <= 1.0)) {
1856                // Solve (x,y) for curve at t = tx to find extrema
1857                final num tprime = 1.0 - t1;
1858                final num extremaX = (tprime * tprime * x1) +
1859                    (2 * t1 * tprime * cpX) +
1860                    (t1 * t1 * x2);
1861                final num extremaY = (tprime * tprime * y1) +
1862                    (2 * t1 * tprime * cpY) +
1863                    (t1 * t1 * y2);
1864                // Expand bounds.
1865                minX = math.min(minX, extremaX);
1866                maxX = math.max(maxX, extremaX);
1867                minY = math.min(minY, extremaY);
1868                maxY = math.max(maxY, extremaY);
1869              }
1870            }
1871            // Now calculate dy/dt = 0
1872            denom = y1 - (2 * cpY) + y2;
1873            if (denom.abs() > epsilon) {
1874              final num t2 = (y1 - cpY) / denom;
1875              if ((t2 >= 0) && (t2 <= 1.0)) {
1876                final num tprime2 = 1.0 - t2;
1877                final num extrema2X = (tprime2 * tprime2 * x1) +
1878                    (2 * t2 * tprime2 * cpX) +
1879                    (t2 * t2 * x2);
1880                final num extrema2Y = (tprime2 * tprime2 * y1) +
1881                    (2 * t2 * tprime2 * cpY) +
1882                    (t2 * t2 * y2);
1883                // Expand bounds.
1884                minX = math.min(minX, extrema2X);
1885                maxX = math.max(maxX, extrema2X);
1886                minY = math.min(minY, extrema2Y);
1887                maxY = math.max(maxY, extrema2Y);
1888              }
1889            }
1890            curX = x2;
1891            curY = y2;
1892            break;
1893          case engine.PathCommandTypes.bezierCurveTo:
1894            final engine.BezierCurveTo cmd = op;
1895            final double startX = curX;
1896            final double startY = curY;
1897            final double cpX1 = cmd.x1;
1898            final double cpY1 = cmd.y1;
1899            final double cpX2 = cmd.x2;
1900            final double cpY2 = cmd.y2;
1901            final double endX = cmd.x3;
1902            final double endY = cmd.y3;
1903            // Bounding box is defined by all points on the curve where
1904            // monotonicity changes.
1905            minX = math.min(startX, endX);
1906            minY = math.min(startY, endY);
1907            maxX = math.max(startX, endX);
1908            maxY = math.max(startY, endY);
1909
1910            double extremaX;
1911            double extremaY;
1912            double a, b, c;
1913
1914            // Check for simple case of strong ordering before calculating
1915            // extrema
1916            if (!(((startX < cpX1) && (cpX1 < cpX2) && (cpX2 < endX)) ||
1917                ((startX > cpX1) && (cpX1 > cpX2) && (cpX2 > endX)))) {
1918              // The extrema point is dx/dt B(t) = 0
1919              // The derivative of B(t) for cubic bezier is a quadratic equation
1920              // with multiple roots
1921              // B'(t) = a*t*t + b*t + c*t
1922              a = -startX + (3 * (cpX1 - cpX2)) + endX;
1923              b = 2 * (startX - (2 * cpX1) + cpX2);
1924              c = -startX + cpX1;
1925
1926              // Now find roots for quadratic equation with known coefficients
1927              // a,b,c
1928              // The roots are (-b+-sqrt(b*b-4*a*c)) / 2a
1929              num s = (b * b) - (4 * a * c);
1930              // If s is negative, we have no real roots
1931              if ((s >= 0.0) && (a.abs() > epsilon)) {
1932                if (s == 0.0) {
1933                  // we have only 1 root
1934                  final num t = -b / (2 * a);
1935                  final num tprime = 1.0 - t;
1936                  if ((t >= 0.0) && (t <= 1.0)) {
1937                    extremaX = ((tprime * tprime * tprime) * startX) +
1938                        ((3 * tprime * tprime * t) * cpX1) +
1939                        ((3 * tprime * t * t) * cpX2) +
1940                        (t * t * t * endX);
1941                    minX = math.min(extremaX, minX);
1942                    maxX = math.max(extremaX, maxX);
1943                  }
1944                } else {
1945                  // we have 2 roots
1946                  s = math.sqrt(s);
1947                  num t = (-b - s) / (2 * a);
1948                  num tprime = 1.0 - t;
1949                  if ((t >= 0.0) && (t <= 1.0)) {
1950                    extremaX = ((tprime * tprime * tprime) * startX) +
1951                        ((3 * tprime * tprime * t) * cpX1) +
1952                        ((3 * tprime * t * t) * cpX2) +
1953                        (t * t * t * endX);
1954                    minX = math.min(extremaX, minX);
1955                    maxX = math.max(extremaX, maxX);
1956                  }
1957                  // check 2nd root
1958                  t = (-b + s) / (2 * a);
1959                  tprime = 1.0 - t;
1960                  if ((t >= 0.0) && (t <= 1.0)) {
1961                    extremaX = ((tprime * tprime * tprime) * startX) +
1962                        ((3 * tprime * tprime * t) * cpX1) +
1963                        ((3 * tprime * t * t) * cpX2) +
1964                        (t * t * t * endX);
1965
1966                    minX = math.min(extremaX, minX);
1967                    maxX = math.max(extremaX, maxX);
1968                  }
1969                }
1970              }
1971            }
1972
1973            // Now calc extremes for dy/dt = 0 just like above
1974            if (!(((startY < cpY1) && (cpY1 < cpY2) && (cpY2 < endY)) ||
1975                ((startY > cpY1) && (cpY1 > cpY2) && (cpY2 > endY)))) {
1976              // The extrema point is dy/dt B(t) = 0
1977              // The derivative of B(t) for cubic bezier is a quadratic equation
1978              // with multiple roots
1979              // B'(t) = a*t*t + b*t + c*t
1980              a = -startY + (3 * (cpY1 - cpY2)) + endY;
1981              b = 2 * (startY - (2 * cpY1) + cpY2);
1982              c = -startY + cpY1;
1983
1984              // Now find roots for quadratic equation with known coefficients
1985              // a,b,c
1986              // The roots are (-b+-sqrt(b*b-4*a*c)) / 2a
1987              num s = (b * b) - (4 * a * c);
1988              // If s is negative, we have no real roots
1989              if ((s >= 0.0) && (a.abs() > epsilon)) {
1990                if (s == 0.0) {
1991                  // we have only 1 root
1992                  final num t = -b / (2 * a);
1993                  final num tprime = 1.0 - t;
1994                  if ((t >= 0.0) && (t <= 1.0)) {
1995                    extremaY = ((tprime * tprime * tprime) * startY) +
1996                        ((3 * tprime * tprime * t) * cpY1) +
1997                        ((3 * tprime * t * t) * cpY2) +
1998                        (t * t * t * endY);
1999                    minY = math.min(extremaY, minY);
2000                    maxY = math.max(extremaY, maxY);
2001                  }
2002                } else {
2003                  // we have 2 roots
2004                  s = math.sqrt(s);
2005                  final num t = (-b - s) / (2 * a);
2006                  final num tprime = 1.0 - t;
2007                  if ((t >= 0.0) && (t <= 1.0)) {
2008                    extremaY = ((tprime * tprime * tprime) * startY) +
2009                        ((3 * tprime * tprime * t) * cpY1) +
2010                        ((3 * tprime * t * t) * cpY2) +
2011                        (t * t * t * endY);
2012                    minY = math.min(extremaY, minY);
2013                    maxY = math.max(extremaY, maxY);
2014                  }
2015                  // check 2nd root
2016                  final num t2 = (-b + s) / (2 * a);
2017                  final num tprime2 = 1.0 - t2;
2018                  if ((t2 >= 0.0) && (t2 <= 1.0)) {
2019                    extremaY = ((tprime2 * tprime2 * tprime2) * startY) +
2020                        ((3 * tprime2 * tprime2 * t2) * cpY1) +
2021                        ((3 * tprime2 * t2 * t2) * cpY2) +
2022                        (t2 * t2 * t2 * endY);
2023                    minY = math.min(extremaY, minY);
2024                    maxY = math.max(extremaY, maxY);
2025                  }
2026                }
2027              }
2028            }
2029            break;
2030          case engine.PathCommandTypes.rect:
2031            final engine.RectCommand cmd = op;
2032            left = cmd.x;
2033            double width = cmd.width;
2034            if (cmd.width < 0) {
2035              left -= width;
2036              width = -width;
2037            }
2038            double top = cmd.y;
2039            double height = cmd.height;
2040            if (cmd.height < 0) {
2041              top -= height;
2042              height = -height;
2043            }
2044            curX = minX = left;
2045            maxX = left + width;
2046            curY = minY = top;
2047            maxY = top + height;
2048            break;
2049          case engine.PathCommandTypes.rRect:
2050            final engine.RRectCommand cmd = op;
2051            final RRect rRect = cmd.rrect;
2052            curX = minX = rRect.left;
2053            maxX = rRect.left + rRect.width;
2054            curY = minY = rRect.top;
2055            maxY = rRect.top + rRect.height;
2056            break;
2057          case engine.PathCommandTypes.close:
2058          default:
2059            skipBounds = false;
2060            break;
2061        }
2062        if (!skipBounds) {
2063          if (!ltrbInitialized) {
2064            left = minX;
2065            right = maxX;
2066            top = minY;
2067            bottom = maxY;
2068            ltrbInitialized = true;
2069          } else {
2070            left = math.min(left, minX);
2071            right = math.max(right, maxX);
2072            top = math.min(top, minY);
2073            bottom = math.max(bottom, maxY);
2074          }
2075        }
2076      }
2077    }
2078    return ltrbInitialized
2079        ? Rect.fromLTRB(left, top, right, bottom)
2080        : Rect.zero;
2081  }
2082
2083  /// Combines the two paths according to the manner specified by the given
2084  /// `operation`.
2085  ///
2086  /// The resulting path will be constructed from non-overlapping contours. The
2087  /// curve order is reduced where possible so that cubics may be turned into
2088  /// quadratics, and quadratics maybe turned into lines.
2089  static Path combine(PathOperation operation, Path path1, Path path2) {
2090    assert(path1 != null);
2091    assert(path2 != null);
2092    throw UnimplementedError();
2093  }
2094
2095  /// Creates a [PathMetrics] object for this path.
2096  ///
2097  /// If `forceClosed` is set to true, the contours of the path will be measured
2098  /// as if they had been closed, even if they were not explicitly closed.
2099  PathMetrics computeMetrics({bool forceClosed = false}) {
2100    return PathMetrics._(this, forceClosed);
2101  }
2102
2103  /// Detects if path is rounded rectangle and returns rounded rectangle or
2104  /// null.
2105  ///
2106  /// Used for web optimization of physical shape represented as
2107  /// a persistent div.
2108  RRect get webOnlyPathAsRoundedRect {
2109    if (subpaths.length != 1) {
2110      return null;
2111    }
2112    final engine.Subpath subPath = subpaths[0];
2113    if (subPath.commands.length != 1) {
2114      return null;
2115    }
2116    final engine.PathCommand command = subPath.commands[0];
2117    return (command is engine.RRectCommand) ? command.rrect : null;
2118  }
2119
2120  /// Detects if path is simple rectangle and returns rectangle or null.
2121  ///
2122  /// Used for web optimization of physical shape represented as
2123  /// a persistent div.
2124  Rect get webOnlyPathAsRect {
2125    if (subpaths.length != 1) {
2126      return null;
2127    }
2128    final engine.Subpath subPath = subpaths[0];
2129    if (subPath.commands.length != 1) {
2130      return null;
2131    }
2132    final engine.PathCommand command = subPath.commands[0];
2133    return (command is engine.RectCommand)
2134        ? Rect.fromLTWH(command.x, command.y, command.width, command.height)
2135        : null;
2136  }
2137
2138  /// Detects if path is simple oval and returns [engine.Ellipse] or null.
2139  ///
2140  /// Used for web optimization of physical shape represented as
2141  /// a persistent div.
2142  engine.Ellipse get webOnlyPathAsCircle {
2143    if (subpaths.length != 1) {
2144      return null;
2145    }
2146    final engine.Subpath subPath = subpaths[0];
2147    if (subPath.commands.length != 1) {
2148      return null;
2149    }
2150    final engine.PathCommand command = subPath.commands[0];
2151    if (command is engine.Ellipse) {
2152      final engine.Ellipse ellipse = command;
2153      if ((ellipse.endAngle - ellipse.startAngle) % (2 * math.pi) == 0.0) {
2154        return ellipse;
2155      }
2156    }
2157    return null;
2158  }
2159
2160  /// Serializes this path to a value that's sent to a CSS custom painter for
2161  /// painting.
2162  List<dynamic> webOnlySerializeToCssPaint() {
2163    final List<dynamic> serializedSubpaths = <dynamic>[];
2164    for (int i = 0; i < subpaths.length; i++) {
2165      serializedSubpaths.add(subpaths[i].serializeToCssPaint());
2166    }
2167    return serializedSubpaths;
2168  }
2169
2170  @override
2171  String toString() {
2172    if (engine.assertionsEnabled) {
2173      return 'Path(${subpaths.join(', ')})';
2174    } else {
2175      return super.toString();
2176    }
2177  }
2178}
2179
2180/// An iterable collection of [PathMetric] objects describing a [Path].
2181///
2182/// A [PathMetrics] object is created by using the [Path.computeMetrics] method,
2183/// and represents the path as it stood at the time of the call. Subsequent
2184/// modifications of the path do not affect the [PathMetrics] object.
2185///
2186/// Each path metric corresponds to a segment, or contour, of a path.
2187///
2188/// For example, a path consisting of a [Path.lineTo], a [Path.moveTo], and
2189/// another [Path.lineTo] will contain two contours and thus be represented by
2190/// two [PathMetric] objects.
2191///
2192/// When iterating across a [PathMetrics]' contours, the [PathMetric] objects
2193/// are only valid until the next one is obtained.
2194class PathMetrics extends IterableBase<PathMetric> {
2195  PathMetrics._(Path path, bool forceClosed)
2196      : _iterator = PathMetricIterator._(PathMetric._(path, forceClosed));
2197
2198  final Iterator<PathMetric> _iterator;
2199
2200  @override
2201  Iterator<PathMetric> get iterator => _iterator;
2202}
2203
2204/// Tracks iteration from one segment of a path to the next for measurement.
2205class PathMetricIterator implements Iterator<PathMetric> {
2206  PathMetricIterator._(this._pathMetric);
2207
2208  PathMetric _pathMetric;
2209  bool _firstTime = true;
2210
2211  @override
2212  PathMetric get current => _firstTime ? null : _pathMetric;
2213
2214  @override
2215  bool moveNext() {
2216    // PathMetric isn't a normal iterable - it's already initialized to its
2217    // first Path.  Should only call _moveNext when done with the first one.
2218    if (_firstTime == true) {
2219      _firstTime = false;
2220      return true;
2221    } else if (_pathMetric?._moveNext() == true) {
2222      return true;
2223    }
2224    _pathMetric = null;
2225    return false;
2226  }
2227}
2228
2229/// Utilities for measuring a [Path] and extracting subpaths.
2230///
2231/// Iterate over the object returned by [Path.computeMetrics] to obtain
2232/// [PathMetric] objects.
2233///
2234/// Once created, metrics will only be valid while the iterator is at the given
2235/// contour. When the next contour's [PathMetric] is obtained, this object
2236/// becomes invalid.
2237class PathMetric {
2238  final Path path;
2239  final bool forceClosed;
2240
2241  /// Create a new empty [Path] object.
2242  PathMetric._(this.path, this.forceClosed);
2243
2244  /// Return the total length of the current contour.
2245  double get length => throw UnimplementedError();
2246
2247  /// Computes the position of hte current contour at the given offset, and the
2248  /// angle of the path at that point.
2249  ///
2250  /// For example, calling this method with a distance of 1.41 for a line from
2251  /// 0.0,0.0 to 2.0,2.0 would give a point 1.0,1.0 and the angle 45 degrees
2252  /// (but in radians).
2253  ///
2254  /// Returns null if the contour has zero [length].
2255  ///
2256  /// The distance is clamped to the [length] of the current contour.
2257  Tangent getTangentForOffset(double distance) {
2258    final Float32List posTan = _getPosTan(distance);
2259    // first entry == 0 indicates that Skia returned false
2260    if (posTan[0] == 0.0) {
2261      return null;
2262    } else {
2263      return Tangent(
2264          Offset(posTan[1], posTan[2]), Offset(posTan[3], posTan[4]));
2265    }
2266  }
2267
2268  Float32List _getPosTan(double distance) => throw UnimplementedError();
2269
2270  /// Given a start and stop distance, return the intervening segment(s).
2271  ///
2272  /// `start` and `end` are pinned to legal values (0..[length])
2273  /// Returns null if the segment is 0 length or `start` > `stop`.
2274  /// Begin the segment with a moveTo if `startWithMoveTo` is true.
2275  Path extractPath(double start, double end, {bool startWithMoveTo = true}) =>
2276      throw UnimplementedError();
2277
2278  /// Whether the contour is closed.
2279  ///
2280  /// Returns true if the contour ends with a call to [Path.close] (which may
2281  /// have been implied when using [Path.addRect]) or if `forceClosed` was
2282  /// specified as true in the call to [Path.computeMetrics].  Returns false
2283  /// otherwise.
2284  bool get isClosed => throw UnimplementedError();
2285
2286  // Move to the next contour in the path.
2287  //
2288  // A path can have a next contour if [Path.moveTo] was called after drawing
2289  // began. Return true if one exists, or false.
2290  //
2291  // This is not exactly congruent with a regular [Iterator.moveNext].
2292  // Typically, [Iterator.moveNext] should be called before accessing the
2293  // [Iterator.current]. In this case, the [PathMetric] is valid before
2294  // calling `_moveNext` - `_moveNext` should be called after the first
2295  // iteration is done instead of before.
2296  bool _moveNext() => throw UnimplementedError();
2297
2298  @override
2299  String toString() => 'PathMetric';
2300}
2301
2302/// The geometric description of a tangent: the angle at a point.
2303///
2304/// See also:
2305///  * [PathMetric.getTangentForOffset], which returns the tangent of an offset
2306///    along a path.
2307class Tangent {
2308  /// Creates a [Tangent] with the given values.
2309  ///
2310  /// The arguments must not be null.
2311  const Tangent(this.position, this.vector)
2312      : assert(position != null),
2313        assert(vector != null);
2314
2315  /// Creates a [Tangent] based on the angle rather than the vector.
2316  ///
2317  /// The [vector] is computed to be the unit vector at the given angle,
2318  /// interpreted as clockwise radians from the x axis.
2319  factory Tangent.fromAngle(Offset position, double angle) {
2320    return Tangent(position, Offset(math.cos(angle), math.sin(angle)));
2321  }
2322
2323  /// Position of the tangent.
2324  ///
2325  /// When used with [PathMetric.getTangentForOffset], this represents the
2326  /// precise position that the given offset along the path corresponds to.
2327  final Offset position;
2328
2329  /// The vector of the curve at [position].
2330  ///
2331  /// When used with [PathMetric.getTangentForOffset], this is the vector of the
2332  /// curve that is at the given offset along the path (i.e. the direction of
2333  /// the curve at [position]).
2334  final Offset vector;
2335
2336  /// The direction of the curve at [position].
2337  ///
2338  /// When used with [PathMetric.getTangentForOffset], this is the angle of the
2339  /// curve that is the given offset along the path (i.e. the direction of the
2340  /// curve at [position]).
2341  ///
2342  /// This value is in radians, with 0.0 meaning pointing along the x axis in
2343  /// the positive x-axis direction, positive numbers pointing downward toward
2344  /// the negative y-axis, i.e. in a clockwise direction, and negative numbers
2345  /// pointing upward toward the positive y-axis, i.e. in a counter-clockwise
2346  /// direction.
2347  // flip the sign to be consistent with [Path.arcTo]'s `sweepAngle`
2348  double get angle => -math.atan2(vector.dy, vector.dx);
2349}
2350
2351class RawRecordingCanvas extends engine.BitmapCanvas
2352    implements PictureRecorder {
2353  RawRecordingCanvas(Size size) : super(Offset.zero & size);
2354
2355  @override
2356  void dispose() {
2357    clear();
2358  }
2359
2360  @override
2361  engine.RecordingCanvas beginRecording(Rect bounds) =>
2362      throw UnsupportedError('');
2363  @override
2364  Picture endRecording() => throw UnsupportedError('');
2365
2366  @override
2367  engine.RecordingCanvas _canvas;
2368
2369  @override
2370  bool _isRecording = true;
2371
2372  @override
2373  bool get isRecording => true;
2374
2375  @override
2376  Rect cullRect;
2377}
2378
2379// Returns true if point is inside ellipse.
2380bool _ellipseContains(double px, double py, double centerX, double centerY,
2381    double radiusX, double radiusY) {
2382  final double dx = px - centerX;
2383  final double dy = py - centerY;
2384  return ((dx * dx) / (radiusX * radiusX)) + ((dy * dy) / (radiusY * radiusY)) <
2385      1.0;
2386}
2387