• 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 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