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 engine; 6 7/// Defines canvas interface common across canvases that the [SceneBuilder] 8/// renders to. 9/// 10/// This can be used either as an interface or super-class. 11abstract class EngineCanvas { 12 /// The element that is attached to the DOM. 13 html.Element get rootElement; 14 15 void dispose() { 16 clear(); 17 } 18 19 void clear(); 20 21 void save(); 22 23 void restore(); 24 25 void translate(double dx, double dy); 26 27 void scale(double sx, double sy); 28 29 void rotate(double radians); 30 31 void skew(double sx, double sy); 32 33 void transform(Float64List matrix4); 34 35 void clipRect(ui.Rect rect); 36 37 void clipRRect(ui.RRect rrect); 38 39 void clipPath(ui.Path path); 40 41 void drawColor(ui.Color color, ui.BlendMode blendMode); 42 43 void drawLine(ui.Offset p1, ui.Offset p2, ui.PaintData paint); 44 45 void drawPaint(ui.PaintData paint); 46 47 void drawRect(ui.Rect rect, ui.PaintData paint); 48 49 void drawRRect(ui.RRect rrect, ui.PaintData paint); 50 51 void drawDRRect(ui.RRect outer, ui.RRect inner, ui.PaintData paint); 52 53 void drawOval(ui.Rect rect, ui.PaintData paint); 54 55 void drawCircle(ui.Offset c, double radius, ui.PaintData paint); 56 57 void drawPath(ui.Path path, ui.PaintData paint); 58 59 void drawShadow( 60 ui.Path path, ui.Color color, double elevation, bool transparentOccluder); 61 62 void drawImage(ui.Image image, ui.Offset p, ui.PaintData paint); 63 64 void drawImageRect( 65 ui.Image image, ui.Rect src, ui.Rect dst, ui.PaintData paint); 66 67 void drawParagraph(EngineParagraph paragraph, ui.Offset offset); 68} 69 70/// Adds an [offset] transformation to a [transform] matrix and returns the 71/// combined result. 72/// 73/// If the given offset is zero, returns [transform] matrix as is. Otherwise, 74/// returns a new [Matrix4] object representing the combined transformation. 75Matrix4 transformWithOffset(Matrix4 transform, ui.Offset offset) { 76 if (offset == ui.Offset.zero) { 77 return transform; 78 } 79 80 // Clone to avoid mutating transform. 81 final Matrix4 effectiveTransform = transform.clone(); 82 effectiveTransform.translate(offset.dx, offset.dy, 0.0); 83 return effectiveTransform; 84} 85 86class _SaveStackEntry { 87 _SaveStackEntry({ 88 @required this.transform, 89 @required this.clipStack, 90 }); 91 92 final Matrix4 transform; 93 final List<_SaveClipEntry> clipStack; 94} 95 96/// Tagged union of clipping parameters used for canvas. 97class _SaveClipEntry { 98 final ui.Rect rect; 99 final ui.RRect rrect; 100 final ui.Path path; 101 final Matrix4 currentTransform; 102 _SaveClipEntry.rect(this.rect, this.currentTransform) 103 : rrect = null, 104 path = null; 105 _SaveClipEntry.rrect(this.rrect, this.currentTransform) 106 : rect = null, 107 path = null; 108 _SaveClipEntry.path(this.path, this.currentTransform) 109 : rect = null, 110 rrect = null; 111} 112 113/// Provides save stack tracking functionality to implementations of 114/// [EngineCanvas]. 115mixin SaveStackTracking on EngineCanvas { 116 static final Vector3 _unitZ = Vector3(0.0, 0.0, 1.0); 117 118 final List<_SaveStackEntry> _saveStack = <_SaveStackEntry>[]; 119 120 /// The stack that maintains clipping operations used when text is painted 121 /// onto bitmap canvas but is composited as separate element. 122 List<_SaveClipEntry> _clipStack; 123 124 /// Returns whether there are active clipping regions on the canvas. 125 bool get isClipped => _clipStack != null; 126 127 /// Empties the save stack and the element stack, and resets the transform 128 /// and clip parameters. 129 /// 130 /// Classes that override this method must call `super.clear()`. 131 @override 132 void clear() { 133 _saveStack.clear(); 134 _clipStack = null; 135 _currentTransform = Matrix4.identity(); 136 } 137 138 /// The current transformation matrix. 139 Matrix4 get currentTransform => _currentTransform; 140 Matrix4 _currentTransform = Matrix4.identity(); 141 142 /// Saves current clip and transform on the save stack. 143 /// 144 /// Classes that override this method must call `super.save()`. 145 @override 146 void save() { 147 _saveStack.add(_SaveStackEntry( 148 transform: _currentTransform.clone(), 149 clipStack: 150 _clipStack == null ? null : List<_SaveClipEntry>.from(_clipStack), 151 )); 152 } 153 154 /// Restores current clip and transform from the save stack. 155 /// 156 /// Classes that override this method must call `super.restore()`. 157 @override 158 void restore() { 159 if (_saveStack.isEmpty) { 160 return; 161 } 162 final _SaveStackEntry entry = _saveStack.removeLast(); 163 _currentTransform = entry.transform; 164 _clipStack = entry.clipStack; 165 } 166 167 /// Multiplies the [currentTransform] matrix by a translation. 168 /// 169 /// Classes that override this method must call `super.translate()`. 170 @override 171 void translate(double dx, double dy) { 172 _currentTransform.translate(dx, dy); 173 } 174 175 /// Scales the [currentTransform] matrix. 176 /// 177 /// Classes that override this method must call `super.scale()`. 178 @override 179 void scale(double sx, double sy) { 180 _currentTransform.scale(sx, sy); 181 } 182 183 /// Rotates the [currentTransform] matrix. 184 /// 185 /// Classes that override this method must call `super.rotate()`. 186 @override 187 void rotate(double radians) { 188 _currentTransform.rotate(_unitZ, radians); 189 } 190 191 /// Skews the [currentTransform] matrix. 192 /// 193 /// Classes that override this method must call `super.skew()`. 194 @override 195 void skew(double sx, double sy) { 196 final Matrix4 skewMatrix = Matrix4.identity(); 197 final Float64List storage = skewMatrix.storage; 198 storage[1] = sy; 199 storage[4] = sx; 200 _currentTransform.multiply(skewMatrix); 201 } 202 203 /// Multiplies the [currentTransform] matrix by another matrix. 204 /// 205 /// Classes that override this method must call `super.transform()`. 206 @override 207 void transform(Float64List matrix4) { 208 _currentTransform.multiply(Matrix4.fromFloat64List(matrix4)); 209 } 210 211 /// Adds a rectangle to clipping stack. 212 /// 213 /// Classes that override this method must call `super.clipRect()`. 214 @override 215 void clipRect(ui.Rect rect) { 216 _clipStack ??= <_SaveClipEntry>[]; 217 _clipStack.add(_SaveClipEntry.rect(rect, _currentTransform.clone())); 218 } 219 220 /// Adds a round rectangle to clipping stack. 221 /// 222 /// Classes that override this method must call `super.clipRRect()`. 223 @override 224 void clipRRect(ui.RRect rrect) { 225 _clipStack ??= <_SaveClipEntry>[]; 226 _clipStack.add(_SaveClipEntry.rrect(rrect, _currentTransform.clone())); 227 } 228 229 /// Adds a path to clipping stack. 230 /// 231 /// Classes that override this method must call `super.clipPath()`. 232 @override 233 void clipPath(ui.Path path) { 234 _clipStack ??= <_SaveClipEntry>[]; 235 _clipStack.add(_SaveClipEntry.path(path, _currentTransform.clone())); 236 } 237} 238 239html.Element _drawParagraphElement( 240 EngineParagraph paragraph, 241 ui.Offset offset, { 242 Matrix4 transform, 243}) { 244 assert(paragraph._isLaidOut); 245 246 final html.Element paragraphElement = paragraph._paragraphElement.clone(true); 247 248 final html.CssStyleDeclaration paragraphStyle = paragraphElement.style; 249 paragraphStyle 250 ..position = 'absolute' 251 ..whiteSpace = 'pre-wrap' 252 ..overflowWrap = 'break-word' 253 ..overflow = 'hidden' 254 ..height = '${paragraph.height}px' 255 ..width = '${paragraph.width}px'; 256 257 if (transform != null) { 258 paragraphStyle 259 ..transformOrigin = '0 0 0' 260 ..transform = 261 matrix4ToCssTransform(transformWithOffset(transform, offset)); 262 } 263 264 final ParagraphGeometricStyle style = paragraph._geometricStyle; 265 266 // TODO(flutter_web): https://github.com/flutter/flutter/issues/33223 267 if (style.ellipsis != null && 268 (style.maxLines == null || style.maxLines == 1)) { 269 paragraphStyle 270 ..whiteSpace = 'pre' 271 ..textOverflow = 'ellipsis'; 272 } 273 return paragraphElement; 274} 275