22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
5- // @dart = 2.10
5+ // @dart = 2.12
66part of engine;
77
88/// Allocates and caches 0 or more canvas(s) for [BitmapCanvas] .
@@ -515,19 +515,28 @@ class _CanvasPool extends _SaveStackTracking {
515515 void strokeLine (ui.Offset p1, ui.Offset p2) {
516516 html.CanvasRenderingContext2D ctx = context;
517517 ctx.beginPath ();
518- ctx.moveTo (p1.dx, p1.dy);
519- ctx.lineTo (p2.dx, p2.dy);
518+ final ui.Rect ? shaderBounds = contextHandle._shaderBounds;
519+ if (shaderBounds == null ) {
520+ ctx.moveTo (p1.dx, p1.dy);
521+ ctx.lineTo (p2.dx, p2.dy);
522+ } else {
523+ ctx.moveTo (p1.dx - shaderBounds.left, p1.dy - shaderBounds.top);
524+ ctx.lineTo (p2.dx - shaderBounds.left, p2.dy - shaderBounds.top);
525+ }
520526 ctx.stroke ();
521527 }
522528
523529 void drawPoints (ui.PointMode pointMode, Float32List points, double radius) {
524530 html.CanvasRenderingContext2D ctx = context;
525531 final int len = points.length;
532+ final ui.Rect ? shaderBounds = contextHandle._shaderBounds;
533+ double offsetX = shaderBounds == null ? 0 : - shaderBounds.left;
534+ double offsetY = shaderBounds == null ? 0 : - shaderBounds.top;
526535 switch (pointMode) {
527536 case ui.PointMode .points:
528537 for (int i = 0 ; i < len; i += 2 ) {
529- final double x = points[i];
530- final double y = points[i + 1 ];
538+ final double x = points[i] + offsetX ;
539+ final double y = points[i + 1 ] + offsetY ;
531540 ctx.beginPath ();
532541 ctx.arc (x, y, radius, 0 , 2.0 * math.pi);
533542 ctx.fill ();
@@ -536,16 +545,16 @@ class _CanvasPool extends _SaveStackTracking {
536545 case ui.PointMode .lines:
537546 ctx.beginPath ();
538547 for (int i = 0 ; i < (len - 2 ); i += 4 ) {
539- ctx.moveTo (points[i], points[i + 1 ]);
540- ctx.lineTo (points[i + 2 ], points[i + 3 ]);
548+ ctx.moveTo (points[i] + offsetX , points[i + 1 ] + offsetY );
549+ ctx.lineTo (points[i + 2 ] + offsetX , points[i + 3 ] + offsetY );
541550 ctx.stroke ();
542551 }
543552 break ;
544553 case ui.PointMode .polygon:
545554 ctx.beginPath ();
546- ctx.moveTo (points[0 ], points[1 ]);
555+ ctx.moveTo (points[0 ] + offsetX , points[1 ] + offsetY );
547556 for (int i = 2 ; i < len; i += 2 ) {
548- ctx.lineTo (points[i], points[i + 1 ]);
557+ ctx.lineTo (points[i] + offsetX , points[i + 1 ] + offsetY );
549558 }
550559 ctx.stroke ();
551560 break ;
@@ -597,39 +606,117 @@ class _CanvasPool extends _SaveStackTracking {
597606 }
598607 }
599608
609+ /// Applies path to drawing context, preparing for fill and other operations.
610+ ///
611+ /// WARNING: Don't refactor _runPath/_runPathWithOffset. Latency sensitive
612+ void _runPathWithOffset (html.CanvasRenderingContext2D ctx, SurfacePath path,
613+ double offsetX, double offsetY) {
614+ ctx.beginPath ();
615+ final Float32List p = _runBuffer;
616+ final PathRefIterator iter = PathRefIterator (path.pathRef);
617+ int verb = 0 ;
618+ while ((verb = iter.next (p)) != SPath .kDoneVerb) {
619+ switch (verb) {
620+ case SPath .kMoveVerb:
621+ ctx.moveTo (p[0 ] + offsetX, p[1 ] + offsetY);
622+ break ;
623+ case SPath .kLineVerb:
624+ ctx.lineTo (p[2 ] + offsetX, p[3 ] + offsetY);
625+ break ;
626+ case SPath .kCubicVerb:
627+ ctx.bezierCurveTo (p[2 ] + offsetX, p[3 ] + offsetY,
628+ p[4 ] + offsetX, p[5 ] + offsetY, p[6 ] + offsetX, p[7 ] + offsetY);
629+ break ;
630+ case SPath .kQuadVerb:
631+ ctx.quadraticCurveTo (p[2 ] + offsetX, p[3 ] + offsetY,
632+ p[4 ] + offsetX, p[5 ] + offsetY);
633+ break ;
634+ case SPath .kConicVerb:
635+ final double w = iter.conicWeight;
636+ Conic conic = Conic (p[0 ], p[1 ], p[2 ], p[3 ], p[4 ], p[5 ], w);
637+ List <ui.Offset > points = conic.toQuads ();
638+ final int len = points.length;
639+ for (int i = 1 ; i < len; i += 2 ) {
640+ final double p1x = points[i].dx;
641+ final double p1y = points[i].dy;
642+ final double p2x = points[i + 1 ].dx;
643+ final double p2y = points[i + 1 ].dy;
644+ ctx.quadraticCurveTo (p1x + offsetX, p1y + offsetY,
645+ p2x + offsetX, p2y + offsetY);
646+ }
647+ break ;
648+ case SPath .kCloseVerb:
649+ ctx.closePath ();
650+ break ;
651+ default :
652+ throw UnimplementedError ('Unknown path verb $verb ' );
653+ }
654+ }
655+ }
656+
600657 void drawRect (ui.Rect rect, ui.PaintingStyle ? style) {
601658 context.beginPath ();
602- context.rect (rect.left, rect.top, rect.width, rect.height);
659+ final ui.Rect ? shaderBounds = contextHandle._shaderBounds;
660+ if (shaderBounds == null ) {
661+ context.rect (rect.left, rect.top, rect.width, rect.height);
662+ } else {
663+ context.rect (rect.left - shaderBounds.left, rect.top - shaderBounds.top,
664+ rect.width, rect.height);
665+ }
603666 contextHandle.paint (style);
604667 }
605668
606669 void drawRRect (ui.RRect roundRect, ui.PaintingStyle ? style) {
607- _RRectToCanvasRenderer (context).render (roundRect);
670+ final ui.Rect ? shaderBounds = contextHandle._shaderBounds;
671+ _RRectToCanvasRenderer (context).render (
672+ shaderBounds == null ? roundRect
673+ : roundRect.shift (ui.Offset (- shaderBounds.left, - shaderBounds.top)));
608674 contextHandle.paint (style);
609675 }
610676
611677 void drawDRRect (ui.RRect outer, ui.RRect inner, ui.PaintingStyle ? style) {
612678 _RRectRenderer renderer = _RRectToCanvasRenderer (context);
613- renderer.render (outer);
614- renderer.render (inner, startNewPath: false , reverse: true );
679+ final ui.Rect ? shaderBounds = contextHandle._shaderBounds;
680+ if (shaderBounds == null ) {
681+ renderer.render (outer);
682+ renderer.render (inner, startNewPath: false , reverse: true );
683+ } else {
684+ final ui.Offset shift = ui.Offset (- shaderBounds.left, - shaderBounds.top);
685+ renderer.render (outer.shift (shift));
686+ renderer.render (inner.shift (shift), startNewPath: false , reverse: true );
687+ }
615688 contextHandle.paint (style);
616689 }
617690
618691 void drawOval (ui.Rect rect, ui.PaintingStyle ? style) {
619692 context.beginPath ();
620- DomRenderer .ellipse (context, rect.center.dx, rect.center.dy, rect.width / 2 ,
693+ ui.Rect ? shaderBounds = contextHandle._shaderBounds;
694+ final double cx = shaderBounds == null ? rect.center.dx :
695+ rect.center.dx - shaderBounds.left;
696+ final double cy = shaderBounds == null ? rect.center.dy :
697+ rect.center.dy - shaderBounds.top;
698+ DomRenderer .ellipse (context, cx, cy, rect.width / 2 ,
621699 rect.height / 2 , 0 , 0 , 2.0 * math.pi, false );
622700 contextHandle.paint (style);
623701 }
624702
625703 void drawCircle (ui.Offset c, double radius, ui.PaintingStyle ? style) {
626704 context.beginPath ();
627- DomRenderer .ellipse (context, c.dx, c.dy, radius, radius, 0 , 0 , 2.0 * math.pi, false );
705+ final ui.Rect ? shaderBounds = contextHandle._shaderBounds;
706+ final double cx = shaderBounds == null ? c.dx : c.dx - shaderBounds.left;
707+ final double cy = shaderBounds == null ? c.dy : c.dy - shaderBounds.top;
708+ DomRenderer .ellipse (context, cx, cy, radius, radius, 0 , 0 , 2.0 * math.pi, false );
628709 contextHandle.paint (style);
629710 }
630711
631712 void drawPath (ui.Path path, ui.PaintingStyle ? style) {
632- _runPath (context, path as SurfacePath );
713+ final ui.Rect ? shaderBounds = contextHandle._shaderBounds;
714+ if (shaderBounds == null ) {
715+ _runPath (context, path as SurfacePath );
716+ } else {
717+ _runPathWithOffset (context, path as SurfacePath ,
718+ - shaderBounds.left, - shaderBounds.top);
719+ }
633720 contextHandle.paintPath (style, path.fillType);
634721 }
635722
@@ -789,6 +876,14 @@ class ContextStateHandle {
789876 ui.MaskFilter ? _currentFilter;
790877 SurfacePaintData ? _lastUsedPaint;
791878
879+ /// Currently active shader bounds.
880+ ///
881+ /// When a paint style uses a shader that produces a pattern, the pattern
882+ /// origin is relative to current transform. Therefore any painting operations
883+ /// will have to reverse the transform to correctly align pattern with
884+ /// drawing bounds.
885+ ui.Rect ? _shaderBounds;
886+
792887 /// The painting state.
793888 ///
794889 /// Used to validate that the [setUpPaint] and [tearDownPaint] are called in
@@ -824,6 +919,9 @@ class ContextStateHandle {
824919 density);
825920 fillStyle = paintStyle;
826921 strokeStyle = paintStyle;
922+ _shaderBounds = shaderBounds;
923+ // Align pattern origin to destination.
924+ context.translate (shaderBounds! .left, shaderBounds.top);
827925 } else if (paint.color != null ) {
828926 final String ? colorString = colorToCssString (paint.color);
829927 fillStyle = colorString;
@@ -906,6 +1004,10 @@ class ContextStateHandle {
9061004 // shadow attributes.
9071005 context.restore ();
9081006 }
1007+ if (_shaderBounds != null ) {
1008+ context.translate (- _shaderBounds! .left, - _shaderBounds! .top);
1009+ _shaderBounds = null ;
1010+ }
9091011 }
9101012
9111013 void paint (ui.PaintingStyle ? style) {
@@ -948,6 +1050,7 @@ class ContextStateHandle {
9481050 _currentStrokeCap = ui.StrokeCap .butt;
9491051 context.lineJoin = 'miter' ;
9501052 _currentStrokeJoin = ui.StrokeJoin .miter;
1053+ _shaderBounds = null ;
9511054 }
9521055}
9531056
0 commit comments