blob: 258ced6479440511fb50d95355659a4e333ca15b [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// For member documentation see https://api.flutter.dev/flutter/dart-ui/Canvas-class.html
// ignore_for_file: public_member_api_docs
import 'dart:html' as html;
import 'dart:typed_data';
import 'package:ui/ui.dart' as ui;
import 'html/painting.dart';
import 'html/render_vertices.dart';
import 'text/paragraph.dart';
import 'util.dart';
import 'vector_math.dart';
/// Defines canvas interface common across canvases that the [SceneBuilder]
/// renders to.
///
/// This can be used either as an interface or super-class.
abstract class EngineCanvas {
/// The element that is attached to the DOM.
html.Element get rootElement;
void dispose() {
clear();
}
void clear();
void save();
void restore();
void translate(double dx, double dy);
void scale(double sx, double sy);
void rotate(double radians);
void skew(double sx, double sy);
void transform(Float32List matrix4);
void clipRect(ui.Rect rect, ui.ClipOp clipOp);
void clipRRect(ui.RRect rrect);
void clipPath(ui.Path path);
void drawColor(ui.Color color, ui.BlendMode blendMode);
void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaintData paint);
void drawPaint(SurfacePaintData paint);
void drawRect(ui.Rect rect, SurfacePaintData paint);
void drawRRect(ui.RRect rrect, SurfacePaintData paint);
void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaintData paint);
void drawOval(ui.Rect rect, SurfacePaintData paint);
void drawCircle(ui.Offset c, double radius, SurfacePaintData paint);
void drawPath(ui.Path path, SurfacePaintData paint);
void drawShadow(
ui.Path path, ui.Color color, double elevation, bool transparentOccluder);
void drawImage(ui.Image image, ui.Offset p, SurfacePaintData paint);
void drawImageRect(
ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaintData paint);
void drawParagraph(EngineParagraph paragraph, ui.Offset offset);
void drawVertices(
SurfaceVertices vertices, ui.BlendMode blendMode, SurfacePaintData paint);
void drawPoints(ui.PointMode pointMode, Float32List points, SurfacePaintData paint);
/// Extension of Canvas API to mark the end of a stream of painting commands
/// to enable re-use/dispose optimizations.
void endOfPaint();
}
/// Adds an [offset] transformation to a [transform] matrix and returns the
/// combined result.
///
/// If the given offset is zero, returns [transform] matrix as is. Otherwise,
/// returns a new [Matrix4] object representing the combined transformation.
Matrix4 transformWithOffset(Matrix4 transform, ui.Offset offset) {
if (offset == ui.Offset.zero) {
return transform;
}
// Clone to avoid mutating transform.
final Matrix4 effectiveTransform = transform.clone();
effectiveTransform.translate(offset.dx, offset.dy, 0.0);
return effectiveTransform;
}
class SaveStackEntry {
SaveStackEntry({
required this.transform,
required this.clipStack,
});
final Matrix4 transform;
final List<SaveClipEntry>? clipStack;
}
/// Tagged union of clipping parameters used for canvas.
class SaveClipEntry {
final ui.Rect? rect;
final ui.RRect? rrect;
final ui.Path? path;
final Matrix4 currentTransform;
SaveClipEntry.rect(this.rect, this.currentTransform)
: rrect = null,
path = null;
SaveClipEntry.rrect(this.rrect, this.currentTransform)
: rect = null,
path = null;
SaveClipEntry.path(this.path, this.currentTransform)
: rect = null,
rrect = null;
}
/// Provides save stack tracking functionality to implementations of
/// [EngineCanvas].
mixin SaveStackTracking on EngineCanvas {
static final Vector3 _unitZ = Vector3(0.0, 0.0, 1.0);
final List<SaveStackEntry> _saveStack = <SaveStackEntry>[];
/// The stack that maintains clipping operations used when text is painted
/// onto bitmap canvas but is composited as separate element.
List<SaveClipEntry>? _clipStack;
/// Returns whether there are active clipping regions on the canvas.
bool get isClipped => _clipStack != null;
/// Empties the save stack and the element stack, and resets the transform
/// and clip parameters.
///
/// Classes that override this method must call `super.clear()`.
@override
void clear() {
_saveStack.clear();
_clipStack = null;
_currentTransform = Matrix4.identity();
}
/// The current transformation matrix.
Matrix4 get currentTransform => _currentTransform;
Matrix4 _currentTransform = Matrix4.identity();
/// Saves current clip and transform on the save stack.
///
/// Classes that override this method must call `super.save()`.
@override
void save() {
_saveStack.add(SaveStackEntry(
transform: _currentTransform.clone(),
clipStack:
_clipStack == null ? null : List<SaveClipEntry>.from(_clipStack!),
));
}
/// Restores current clip and transform from the save stack.
///
/// Classes that override this method must call `super.restore()`.
@override
void restore() {
if (_saveStack.isEmpty) {
return;
}
final SaveStackEntry entry = _saveStack.removeLast();
_currentTransform = entry.transform;
_clipStack = entry.clipStack;
}
/// Multiplies the [currentTransform] matrix by a translation.
///
/// Classes that override this method must call `super.translate()`.
@override
void translate(double dx, double dy) {
_currentTransform.translate(dx, dy);
}
/// Scales the [currentTransform] matrix.
///
/// Classes that override this method must call `super.scale()`.
@override
void scale(double sx, double sy) {
_currentTransform.scale(sx, sy);
}
/// Rotates the [currentTransform] matrix.
///
/// Classes that override this method must call `super.rotate()`.
@override
void rotate(double radians) {
_currentTransform.rotate(_unitZ, radians);
}
/// Skews the [currentTransform] matrix.
///
/// Classes that override this method must call `super.skew()`.
@override
void skew(double sx, double sy) {
final Matrix4 skewMatrix = Matrix4.identity();
final Float32List storage = skewMatrix.storage;
storage[1] = sy;
storage[4] = sx;
_currentTransform.multiply(skewMatrix);
}
/// Multiplies the [currentTransform] matrix by another matrix.
///
/// Classes that override this method must call `super.transform()`.
@override
void transform(Float32List matrix4) {
_currentTransform.multiply(Matrix4.fromFloat32List(matrix4));
}
/// Adds a rectangle to clipping stack.
///
/// Classes that override this method must call `super.clipRect()`.
@override
void clipRect(ui.Rect rect, ui.ClipOp op) {
_clipStack ??= <SaveClipEntry>[];
_clipStack!.add(SaveClipEntry.rect(rect, _currentTransform.clone()));
}
/// Adds a round rectangle to clipping stack.
///
/// Classes that override this method must call `super.clipRRect()`.
@override
void clipRRect(ui.RRect rrect) {
_clipStack ??= <SaveClipEntry>[];
_clipStack!.add(SaveClipEntry.rrect(rrect, _currentTransform.clone()));
}
/// Adds a path to clipping stack.
///
/// Classes that override this method must call `super.clipPath()`.
@override
void clipPath(ui.Path path) {
_clipStack ??= <SaveClipEntry>[];
_clipStack!.add(SaveClipEntry.path(path, _currentTransform.clone()));
}
}
html.Element drawParagraphElement(
EngineParagraph paragraph,
ui.Offset offset, {
Matrix4? transform,
}) {
assert(paragraph.isLaidOut);
final html.HtmlElement paragraphElement = paragraph.toDomElement();
if (transform != null) {
setElementTransform(
paragraphElement,
transformWithOffset(transform, offset).storage,
);
}
return paragraphElement;
}
class _SaveElementStackEntry {
_SaveElementStackEntry({
required this.savedElement,
required this.transform,
});
final html.Element savedElement;
final Matrix4 transform;
}
/// Provides save stack tracking functionality to implementations of
/// [EngineCanvas].
mixin SaveElementStackTracking on EngineCanvas {
static final Vector3 _unitZ = Vector3(0.0, 0.0, 1.0);
final List<_SaveElementStackEntry> _saveStack = <_SaveElementStackEntry>[];
/// The element at the top of the element stack, or [rootElement] if the stack
/// is empty.
html.Element get currentElement =>
_elementStack.isEmpty ? rootElement : _elementStack.last;
/// The stack that maintains the DOM elements used to express certain paint
/// operations, such as clips.
final List<html.Element> _elementStack = <html.Element>[];
/// Pushes the [element] onto the element stack for the purposes of applying
/// a paint effect using a DOM element, e.g. for clipping.
///
/// The [restore] method automatically pops the element off the stack.
void pushElement(html.Element element) {
_elementStack.add(element);
}
/// Empties the save stack and the element stack, and resets the transform
/// and clip parameters.
///
/// Classes that override this method must call `super.clear()`.
@override
void clear() {
_saveStack.clear();
_elementStack.clear();
_currentTransform = Matrix4.identity();
}
/// The current transformation matrix.
Matrix4 get currentTransform => _currentTransform;
Matrix4 _currentTransform = Matrix4.identity();
/// Saves current clip and transform on the save stack.
///
/// Classes that override this method must call `super.save()`.
@override
void save() {
_saveStack.add(_SaveElementStackEntry(
savedElement: currentElement,
transform: _currentTransform.clone(),
));
}
/// Restores current clip and transform from the save stack.
///
/// Classes that override this method must call `super.restore()`.
@override
void restore() {
if (_saveStack.isEmpty) {
return;
}
final _SaveElementStackEntry entry = _saveStack.removeLast();
_currentTransform = entry.transform;
// Pop out of any clips.
while (currentElement != entry.savedElement) {
_elementStack.removeLast();
}
}
/// Multiplies the [currentTransform] matrix by a translation.
///
/// Classes that override this method must call `super.translate()`.
@override
void translate(double dx, double dy) {
_currentTransform.translate(dx, dy);
}
/// Scales the [currentTransform] matrix.
///
/// Classes that override this method must call `super.scale()`.
@override
void scale(double sx, double sy) {
_currentTransform.scale(sx, sy);
}
/// Rotates the [currentTransform] matrix.
///
/// Classes that override this method must call `super.rotate()`.
@override
void rotate(double radians) {
_currentTransform.rotate(_unitZ, radians);
}
/// Skews the [currentTransform] matrix.
///
/// Classes that override this method must call `super.skew()`.
@override
void skew(double sx, double sy) {
// DO NOT USE Matrix4.skew(sx, sy)! It treats sx and sy values as radians,
// but in our case they are transform matrix values.
final Matrix4 skewMatrix = Matrix4.identity();
final Float32List storage = skewMatrix.storage;
storage[1] = sy;
storage[4] = sx;
_currentTransform.multiply(skewMatrix);
}
/// Multiplies the [currentTransform] matrix by another matrix.
///
/// Classes that override this method must call `super.transform()`.
@override
void transform(Float32List matrix4) {
_currentTransform.multiply(Matrix4.fromFloat32List(matrix4));
}
}