diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 5bb1a5127399316d26a500e534c7eb3fbbe7e4b7..1c0e2765a2c1353297101ccb338485e56c7f3063 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -419,7 +419,6 @@ FILE: ../../../flutter/lib/ui/window/viewport_metrics.cc FILE: ../../../flutter/lib/ui/window/viewport_metrics.h FILE: ../../../flutter/lib/ui/window/window.cc FILE: ../../../flutter/lib/ui/window/window.h -FILE: ../../../flutter/lib/web_ui/lib/assets/houdini_painter.js FILE: ../../../flutter/lib/web_ui/lib/src/engine.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/alarm_clock.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/assets.dart @@ -463,7 +462,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/history.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/houdini_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html_image_codec.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart diff --git a/lib/web_ui/lib/assets/houdini_painter.js b/lib/web_ui/lib/assets/houdini_painter.js deleted file mode 100644 index 74ec4a29e25cb8356c0df802dae24f9e12ec127a..0000000000000000000000000000000000000000 --- a/lib/web_ui/lib/assets/houdini_painter.js +++ /dev/null @@ -1,1069 +0,0 @@ -// 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. - -// TODO(yjbanov): Consider the following optimizations: -// - Switch from JSON to typed arrays. See: -// https://github.com/w3c/css-houdini-drafts/issues/136 -// - When there is no DOM-rendered content, then clipping in the canvas is more -// efficient than DOM-rendered clipping. -// - When DOM-rendered clip is the only option, then clipping _again_ in the -// canvas is superfluous. -// - When transform is a 2D transform and there is no DOM-rendered content, then -// canvas transform is more efficient than DOM-rendered transform. -// - If a transform must be DOM-rendered, then clipping in the canvas _again_ is -// superfluous. - -/** - * Applies paint commands to CSS Paint API (a.k.a. Houdini). - * - * This painter is driven by houdini_canvas.dart. This painter and the - * HoudiniCanvas class must be kept in sync with each other. - */ -class FlutterPainter { - /** - * Properties used by this painter. - * - * @return {string[]} list of CSS properties this painter depends on. - */ - static get inputProperties() { - return ['--flt']; - } - - /** - * Implements the painter interface. - */ - paint(ctx, geom, properties) { - let fltProp = properties.get('--flt').toString(); - if (!fltProp) { - // Nothing to paint. - return; - } - const commands = JSON.parse(fltProp); - for (let i = 0; i < commands.length; i++) { - let command = commands[i]; - // TODO(yjbanov): we should probably move command identifiers into an enum - switch (command[0]) { - case 1: - this._save(ctx, geom, command); - break; - case 2: - this._restore(ctx, geom, command); - break; - case 3: - this._translate(ctx, geom, command); - break; - case 4: - this._scale(ctx, geom, command); - break; - case 5: - this._rotate(ctx, geom, command); - break; - // Skip case 6: implemented in the DOM for now. - case 7: - this._skew(ctx, geom, command); - break; - case 8: - this._clipRect(ctx, geom, command); - break; - case 9: - this._clipRRect(ctx, geom, command); - break; - case 10: - this._clipPath(ctx, geom, command); - break; - case 11: - this._drawColor(ctx, geom, command); - break; - case 12: - this._drawLine(ctx, geom, command); - break; - case 13: - this._drawPaint(ctx, geom, command); - break; - case 14: - this._drawRect(ctx, geom, command); - break; - case 15: - this._drawRRect(ctx, geom, command); - break; - case 16: - this._drawDRRect(ctx, geom, command); - break; - case 17: - this._drawOval(ctx, geom, command); - break; - case 18: - this._drawCircle(ctx, geom, command); - break; - case 19: - this._drawPath(ctx, geom, command); - break; - case 20: - this._drawShadow(ctx, geom, command); - break; - default: - throw new Error(`Unsupported command ID: ${command[0]}`); - } - } - } - - _applyPaint(ctx, paint) { - let blendMode = _stringForBlendMode(paint.blendMode); - ctx.globalCompositeOperation = blendMode ? blendMode : 'source-over'; - ctx.lineWidth = paint.strokeWidth ? paint.strokeWidth : 1.0; - - let strokeCap = _stringForStrokeCap(paint.strokeCap); - ctx.lineCap = strokeCap ? strokeCap : 'butt'; - - if (paint.shader != null) { - let paintStyle = paint.shader.createPaintStyle(ctx); - ctx.fillStyle = paintStyle; - ctx.strokeStyle = paintStyle; - } else if (paint.color != null) { - let colorString = paint.color; - ctx.fillStyle = colorString; - ctx.strokeStyle = colorString; - } - if (paint.maskFilter != null) { - ctx.filter = `blur(${paint.maskFilter[1]}px)`; - } - } - - _strokeOrFill(ctx, paint, resetPaint) { - switch (paint.style) { - case PaintingStyle.stroke: - ctx.stroke(); - break; - case PaintingStyle.fill: - default: - ctx.fill(); - break; - } - if (resetPaint) { - this._resetPaint(ctx); - } - } - - _resetPaint(ctx) { - ctx.globalCompositeOperation = 'source-over'; - ctx.lineWidth = 1.0; - ctx.lineCap = 'butt'; - ctx.filter = 'none'; - ctx.fillStyle = null; - ctx.strokeStyle = null; - } - - _save(ctx, geom, command) { - ctx.save(); - } - - _restore(ctx, geom, command) { - ctx.restore(); - } - - _translate(ctx, geom, command) { - ctx.translate(command[1], command[2]); - } - - _scale(ctx, geom, command) { - ctx.translate(command[1], command[2]); - } - - _rotate(ctx, geom, command) { - ctx.rotate(command[1]); - } - - _skew(ctx, geom, command) { - ctx.translate(command[1], command[2]); - } - - _drawRect(ctx, geom, command) { - let scanner = _scanCommand(command); - let rect = scanner.scanRect(); - let paint = scanner.scanPaint(); - this._applyPaint(ctx, paint); - ctx.beginPath(); - ctx.rect(rect.left, rect.top, rect.width(), rect.height()); - this._strokeOrFill(ctx, paint, true); - } - - _drawRRect(ctx, geom, command) { - let scanner = _scanCommand(command); - let rrect = scanner.scanRRect(); - let paint = scanner.scanPaint(); - - this._applyPaint(ctx, paint); - this._drawRRectPath(ctx, rrect, true); - this._strokeOrFill(ctx, paint, true); - } - - _drawDRRect(ctx, geom, command) { - let scanner = _scanCommand(command); - let outer = scanner.scanRRect(); - let inner = scanner.scanRRect(); - let paint = scanner.scanPaint(); - this._applyPaint(ctx, paint); - this._drawRRectPath(ctx, outer, true); - this._drawRRectPathReverse(ctx, inner, false); - this._strokeOrFill(ctx, paint, true); - } - - _drawRRectPath(ctx, rrect, startNewPath) { - // TODO(mdebbar): there's a bug in this code, it doesn't correctly handle - // the case when the radius is greater than the width of the - // rect. When we fix that in BitmapCanvas, we need to fix it - // here too. - // To draw the rounded rectangle, perform the following 8 steps: - // 1. draw the line for the top - // 2. draw the arc for the top-right corner - // 3. draw the line for the right side - // 4. draw the arc for the bottom-right corner - // 5. draw the line for the bottom of the rectangle - // 6. draw the arc for the bottom-left corner - // 7. draw the line for the left side - // 8. draw the arc for the top-left corner - // - // After drawing, the current point will be the left side of the top of the - // rounded rectangle (after the corner). - // TODO(het): Confirm that this is the end point in Flutter for RRect - - if (startNewPath) { - ctx.beginPath(); - } - - ctx.moveTo(rrect.left + rrect.trRadiusX, rrect.top); - - // Top side and top-right corner - ctx.lineTo(rrect.right - rrect.trRadiusX, rrect.top); - ctx.ellipse( - rrect.right - rrect.trRadiusX, - rrect.top + rrect.trRadiusY, - rrect.trRadiusX, - rrect.trRadiusY, - 0, - 1.5 * Math.PI, - 2.0 * Math.PI, - false, - ); - - // Right side and bottom-right corner - ctx.lineTo(rrect.right, rrect.bottom - rrect.brRadiusY); - ctx.ellipse( - rrect.right - rrect.brRadiusX, - rrect.bottom - rrect.brRadiusY, - rrect.brRadiusX, - rrect.brRadiusY, - 0, - 0, - 0.5 * Math.PI, - false, - ); - - // Bottom side and bottom-left corner - ctx.lineTo(rrect.left + rrect.blRadiusX, rrect.bottom); - ctx.ellipse( - rrect.left + rrect.blRadiusX, - rrect.bottom - rrect.blRadiusY, - rrect.blRadiusX, - rrect.blRadiusY, - 0, - 0.5 * Math.PI, - Math.PI, - false, - ); - - // Left side and top-left corner - ctx.lineTo(rrect.left, rrect.top + rrect.tlRadiusY); - ctx.ellipse( - rrect.left + rrect.tlRadiusX, - rrect.top + rrect.tlRadiusY, - rrect.tlRadiusX, - rrect.tlRadiusY, - 0, - Math.PI, - 1.5 * Math.PI, - false, - ); - } - - _drawRRectPathReverse(ctx, rrect, startNewPath) { - // Draw the rounded rectangle, counterclockwise. - ctx.moveTo(rrect.right - rrect.trRadiusX, rrect.top); - - if (startNewPath) { - ctx.beginPath(); - } - - // Top side and top-left corner - ctx.lineTo(rrect.left + rrect.tlRadiusX, rrect.top); - ctx.ellipse( - rrect.left + rrect.tlRadiusX, - rrect.top + rrect.tlRadiusY, - rrect.tlRadiusX, - rrect.tlRadiusY, - 0, - 1.5 * Math.PI, - Math.PI, - true, - ); - - // Left side and bottom-left corner - ctx.lineTo(rrect.left, rrect.bottom - rrect.blRadiusY); - ctx.ellipse( - rrect.left + rrect.blRadiusX, - rrect.bottom - rrect.blRadiusY, - rrect.blRadiusX, - rrect.blRadiusY, - 0, - Math.PI, - 0.5 * Math.PI, - true, - ); - - // Bottom side and bottom-right corner - ctx.lineTo(rrect.right - rrect.brRadiusX, rrect.bottom); - ctx.ellipse( - rrect.right - rrect.brRadiusX, - rrect.bottom - rrect.brRadiusY, - rrect.brRadiusX, - rrect.brRadiusY, - 0, - 0.5 * Math.PI, - 0, - true, - ); - - // Right side and top-right corner - ctx.lineTo(rrect.right, rrect.top + rrect.trRadiusY); - ctx.ellipse( - rrect.right - rrect.trRadiusX, - rrect.top + rrect.trRadiusY, - rrect.trRadiusX, - rrect.trRadiusY, - 0, - 0, - 1.5 * Math.PI, - true, - ); - } - - _clipRect(ctx, geom, command) { - let scanner = _scanCommand(command); - let rect = scanner.scanRect(); - ctx.beginPath(); - ctx.rect(rect.left, rect.top, rect.width(), rect.height()); - ctx.clip(); - } - - _clipRRect(ctx, geom, command) { - let path = new Path([]); - let commands = [new RRectCommand(command[1])]; - path.subpaths.push(new Subpath(commands)); - this._runPath(ctx, path); - ctx.clip(); - } - - _clipPath(ctx, geom, command) { - let scanner = _scanCommand(command); - let path = scanner.scanPath(); - this._runPath(ctx, path); - ctx.clip(); - } - - _drawCircle(ctx, geom, command) { - let scanner = _scanCommand(command); - let dx = scanner.scanNumber(); - let dy = scanner.scanNumber(); - let radius = scanner.scanNumber(); - let paint = scanner.scanPaint(); - - this._applyPaint(ctx, paint); - ctx.beginPath(); - ctx.ellipse(dx, dy, radius, radius, 0, 0, 2.0 * Math.PI, false); - this._strokeOrFill(ctx, paint, true); - } - - _drawOval(ctx, geom, command) { - let scanner = _scanCommand(command); - let rect = scanner.scanRect(); - let paint = scanner.scanPaint(); - - this._applyPaint(ctx, paint); - ctx.beginPath(); - ctx.ellipse( - (rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2, - rect.width / 2, rect.height / 2, 0, 0, 2.0 * Math.PI, false); - this._strokeOrFill(ctx, paint, true); - } - - _drawPath(ctx, geom, command) { - let scanner = _scanCommand(command); - let path = scanner.scanPath(); - let paint = scanner.scanPaint(); - this._applyPaint(ctx, paint); - this._runPath(ctx, path); - this._strokeOrFill(ctx, paint, true); - } - - _drawShadow(ctx, geom, command) { - // TODO: this is mostly a stub; implement properly. - let scanner = _scanCommand(command); - let path = scanner.scanPath(); - let color = scanner.scanArray(); - let elevation = scanner.scanNumber(); - let transparentOccluder = scanner.scanBool(); - - let shadows = _computeShadowsForElevation(elevation, color); - for (let i = 0; i < shadows.length; i++) { - let shadow = shadows[i]; - - let paint = new Paint( - null, // blendMode - PaintingStyle.fill, // style - 1.0, // strokeWidth - null, // strokeCap - true, // isAntialias - shadow.color, // color - null, // shader - [BlurStyle.normal, shadow.blur], // maskFilter - null, // filterQuality - null // colorFilter - ); - - ctx.save(); - ctx.translate(shadow.offsetX, shadow.offsetY); - this._applyPaint(ctx, paint); - this._runPath(ctx, path, true); - this._strokeOrFill(ctx, paint, false); - ctx.restore(); - } - this._resetPaint(ctx); - } - - _runPath(ctx, path) { - ctx.beginPath(); - for (let i = 0; i < path.subpaths.length; i++) { - let subpath = path.subpaths[i]; - for (let j = 0; j < subpath.commands.length; j++) { - let command = subpath.commands[j]; - switch (command.type()) { - case PathCommandType.bezierCurveTo: - ctx.bezierCurveTo( - command.x1, command.y1, command.x2, command.y2, command.x3, - command.y3); - break; - case PathCommandType.close: - ctx.closePath(); - break; - case PathCommandType.ellipse: - ctx.ellipse( - command.x, command.y, command.radiusX, command.radiusY, - command.rotation, command.startAngle, command.endAngle, - command.anticlockwise); - break; - case PathCommandType.lineTo: - ctx.lineTo(command.x, command.y); - break; - case PathCommandType.moveTo: - ctx.moveTo(command.x, command.y); - break; - case PathCommandType.rrect: - this._drawRRectPath(ctx, command.rrect, false); - break; - case PathCommandType.rect: - ctx.rect(command.x, command.y, command.width, command.height); - break; - case PathCommandType.quadraticCurveTo: - ctx.quadraticCurveTo( - command.x1, command.y1, command.x2, command.y2); - break; - default: - throw new Error(`Unknown path command ${command.type()}`); - } - } - } - } - - _drawColor(ctx, geom, command) { - ctx.globalCompositeOperation = _stringForBlendMode(command[2]); - ctx.fillStyle = command[1]; - - // Fill a virtually infinite rect with the color. - // - // We can't use (0, 0, width, height) because the current transform can - // cause it to not fill the entire clip. - ctx.fillRect(-10000, -10000, 20000, 20000); - this._resetPaint(ctx); - } - - _drawLine(ctx, geom, command) { - let scanner = _scanCommand(command); - let p1dx = scanner.scanNumber(); - let p1dy = scanner.scanNumber(); - let p2dx = scanner.scanNumber(); - let p2dy = scanner.scanNumber(); - let paint = scanner.scanPaint(); - this._applyPaint(ctx, paint); - ctx.beginPath(); - ctx.moveTo(p1dx, p1dy); - ctx.lineTo(p2dx, p2dy); - ctx.stroke(); - this._resetPaint(ctx); - } - - _drawPaint(ctx, geom, command) { - let scanner = _scanCommand(command); - let paint = scanner.scanPaint(); - this._applyPaint(ctx, paint); - ctx.beginPath(); - - // Fill a virtually infinite rect with the color. - // - // We can't use (0, 0, width, height) because the current transform can - // cause it to not fill the entire clip. - ctx.fillRect(-10000, -10000, 20000, 20000); - this._resetPaint(ctx); - } -} - -function _scanCommand(command) { - return new CommandScanner(command); -} - -const PaintingStyle = { - fill: 0, - stroke: 1, -}; - -/// A singleton used to parse serialized commands. -class CommandScanner { - constructor(command) { - // Skip the first element, which is always the command ID. - this.index = 1; - this.command = command; - } - - scanRect() { - let rect = this.command[this.index++]; - return new Rect(rect[0], rect[1], rect[2], rect[3]); - } - - scanRRect() { - let rrect = this.command[this.index++]; - return new RRect( - rrect[0], rrect[1], rrect[2], rrect[3], rrect[4], rrect[5], rrect[6], - rrect[7], rrect[8], rrect[9], rrect[10], rrect[11]); - } - - scanPaint() { - let paint = this.command[this.index++]; - return new Paint( - paint[0], paint[1], paint[2], paint[3], paint[4], paint[5], paint[6], - paint[7], paint[8], paint[9]); - } - - scanNumber() { - return this.command[this.index++]; - } - - scanString() { - return this.command[this.index++]; - } - - scanBool() { - return this.command[this.index++]; - } - - scanPath() { - let subpaths = this.command[this.index++]; - return new Path(subpaths); - } - - scanArray() { - return this.command[this.index++]; - } -} - -class Rect { - constructor(left, top, right, bottom) { - this.left = left; - this.top = top; - this.right = right; - this.bottom = bottom; - } - - width() { - return this.right - this.left; - } - - height() { - return this.bottom - this.top; - } -} - -class RRect { - constructor( - left, top, right, bottom, tlRadiusX, tlRadiusY, trRadiusX, trRadiusY, - brRadiusX, brRadiusY, blRadiusX, blRadiusY) { - this.left = left; - this.top = top; - this.right = right; - this.bottom = bottom; - this.tlRadiusX = tlRadiusX; - this.tlRadiusY = tlRadiusY; - this.trRadiusX = trRadiusX; - this.trRadiusY = trRadiusY; - this.brRadiusX = brRadiusX; - this.brRadiusY = brRadiusY; - this.blRadiusX = blRadiusX; - this.blRadiusY = blRadiusY; - } - - tallMiddleRect() { - let leftRadius = Math.max(this.blRadiusX, this.tlRadiusX); - let rightRadius = Math.max(this.trRadiusX, this.brRadiusX); - return new Rect( - this.left + leftRadius, this.top, this.right - rightRadius, - this.bottom); - } - - middleRect() { - let leftRadius = Math.max(this.blRadiusX, this.tlRadiusX); - let topRadius = Math.max(this.tlRadiusY, this.trRadiusY); - let rightRadius = Math.max(this.trRadiusX, this.brRadiusX); - let bottomRadius = Math.max(this.brRadiusY, this.blRadiusY); - return new Rect( - this.left + leftRadius, this.top + topRadius, this.right - rightRadius, - this.bottom - bottomRadius); - } - - wideMiddleRect() { - let topRadius = Math.max(this.tlRadiusY, this.trRadiusY); - let bottomRadius = Math.max(this.brRadiusY, this.blRadiusY); - return new Rect( - this.left, this.top + topRadius, this.right, - this.bottom - bottomRadius); - } -} - -class Paint { - constructor( - blendMode, style, strokeWidth, strokeCap, isAntialias, color, shader, - maskFilter, filterQuality, colorFilter) { - this.blendMode = blendMode; - this.style = style; - this.strokeWidth = strokeWidth; - this.strokeCap = strokeCap; - this.isAntialias = isAntialias; - this.color = color; - this.shader = _deserializeShader(shader); // TODO: deserialize - this.maskFilter = maskFilter; - this.filterQuality = filterQuality; - this.colorFilter = colorFilter; // TODO: deserialize - } -} - -function _deserializeShader(data) { - if (!data) { - return null; - } - - switch (data[0]) { - case 1: - return new GradientLinear(data); - default: - throw new Error(`Shader type not supported: ${data}`); - } -} - -class GradientLinear { - constructor(data) { - this.fromX = data[1]; - this.fromY = data[2]; - this.toX = data[3]; - this.toY = data[4]; - this.colors = data[5]; - this.colorStops = data[6]; - this.tileMode = data[7]; - } - - createPaintStyle(ctx) { - let gradient = - ctx.createLinearGradient(this.fromX, this.fromY, this.toX, this.toY); - if (this.colorStops == null) { - gradient.addColorStop(0, this.colors[0]); - gradient.addColorStop(1, this.colors[1]); - return gradient; - } - for (let i = 0; i < this.colors.length; i++) { - gradient.addColorStop(this.colorStops[i], this.colors[i]); - } - return gradient; - } -} - -const BlendMode = { - clear: 0, - src: 1, - dst: 2, - srcOver: 3, - dstOver: 4, - srcIn: 5, - dstIn: 6, - srcOut: 7, - dstOut: 8, - srcATop: 9, - dstATop: 10, - xor: 11, - plus: 12, - modulate: 13, - screen: 14, - overlay: 15, - darken: 16, - lighten: 17, - colorDodge: 18, - colorBurn: 19, - hardLight: 20, - softLight: 21, - difference: 22, - exclusion: 23, - multiply: 24, - hue: 25, - saturation: 26, - color: 27, - luminosity: 28, -}; - -function _stringForBlendMode(blendMode) { - if (blendMode == null) return null; - switch (blendMode) { - case BlendMode.srcOver: - return 'source-over'; - case BlendMode.srcIn: - return 'source-in'; - case BlendMode.srcOut: - return 'source-out'; - case BlendMode.srcATop: - return 'source-atop'; - case BlendMode.dstOver: - return 'destination-over'; - case BlendMode.dstIn: - return 'destination-in'; - case BlendMode.dstOut: - return 'destination-out'; - case BlendMode.dstATop: - return 'destination-atop'; - case BlendMode.plus: - return 'lighten'; - case BlendMode.src: - return 'copy'; - case BlendMode.xor: - return 'xor'; - case BlendMode.multiply: - // Falling back to multiply, ignoring alpha channel. - // TODO(flutter_web): only used for debug, find better fallback for web. - case BlendMode.modulate: - return 'multiply'; - case BlendMode.screen: - return 'screen'; - case BlendMode.overlay: - return 'overlay'; - case BlendMode.darken: - return 'darken'; - case BlendMode.lighten: - return 'lighten'; - case BlendMode.colorDodge: - return 'color-dodge'; - case BlendMode.colorBurn: - return 'color-burn'; - case BlendMode.hardLight: - return 'hard-light'; - case BlendMode.softLight: - return 'soft-light'; - case BlendMode.difference: - return 'difference'; - case BlendMode.exclusion: - return 'exclusion'; - case BlendMode.hue: - return 'hue'; - case BlendMode.saturation: - return 'saturation'; - case BlendMode.color: - return 'color'; - case BlendMode.luminosity: - return 'luminosity'; - default: - throw new Error( - 'Flutter web does not support the blend mode: $blendMode'); - } -} - -const StrokeCap = { - butt: 0, - round: 1, - square: 2, -}; - -function _stringForStrokeCap(strokeCap) { - if (strokeCap == null) return null; - switch (strokeCap) { - case StrokeCap.butt: - return 'butt'; - case StrokeCap.round: - return 'round'; - case StrokeCap.square: - default: - return 'square'; - } -} - -class Path { - constructor(serializedSubpaths) { - this.subpaths = []; - for (let i = 0; i < serializedSubpaths.length; i++) { - let subpath = serializedSubpaths[i]; - let pathCommands = []; - for (let j = 0; j < subpath.length; j++) { - let pathCommand = subpath[j]; - switch (pathCommand[0]) { - case 1: - pathCommands.push(new MoveTo(pathCommand)); - break; - case 2: - pathCommands.push(new LineTo(pathCommand)); - break; - case 3: - pathCommands.push(new Ellipse(pathCommand)); - break; - case 4: - pathCommands.push(new QuadraticCurveTo(pathCommand)); - break; - case 5: - pathCommands.push(new BezierCurveTo(pathCommand)); - break; - case 6: - pathCommands.push(new RectCommand(pathCommand)); - break; - case 7: - pathCommands.push(new RRectCommand(pathCommand)); - break; - case 8: - pathCommands.push(new CloseCommand()); - break; - default: - throw new Error(`Unsupported path command: ${pathCommand}`); - } - } - - this.subpaths.push(new Subpath(pathCommands)); - } - } -} - -class Subpath { - constructor(commands) { - this.commands = commands; - } -} - -class MoveTo { - constructor(data) { - this.x = data[1]; - this.y = data[2]; - } - - type() { - return PathCommandType.moveTo; - } -} - -class LineTo { - constructor(data) { - this.x = data[1]; - this.y = data[2]; - } - - type() { - return PathCommandType.lineTo; - } -} - -class Ellipse { - constructor(data) { - this.x = data[1]; - this.y = data[2]; - this.radiusX = data[3]; - this.radiusY = data[4]; - this.rotation = data[5]; - this.startAngle = data[6]; - this.endAngle = data[7]; - this.anticlockwise = data[8]; - } - - type() { - return PathCommandType.ellipse; - } -} - -class QuadraticCurveTo { - constructor(data) { - this.x1 = data[1]; - this.y1 = data[2]; - this.x2 = data[3]; - this.y2 = data[4]; - } - - type() { - return PathCommandType.quadraticCurveTo; - } -} - -class BezierCurveTo { - constructor(data) { - this.x1 = data[1]; - this.y1 = data[2]; - this.x2 = data[3]; - this.y2 = data[4]; - this.x3 = data[5]; - this.y3 = data[6]; - } - - type() { - return PathCommandType.bezierCurveTo; - } -} - -class RectCommand { - constructor(data) { - this.x = data[1]; - this.y = data[2]; - this.width = data[3]; - this.height = data[4]; - } - - type() { - return PathCommandType.rect; - } -} - -class RRectCommand { - constructor(data) { - let scanner = _scanCommand(data); - this.rrect = scanner.scanRRect(); - } - - type() { - return PathCommandType.rrect; - } -} - -class CloseCommand { - type() { - return PathCommandType.close; - } -} - -class CanvasShadow { - constructor(offsetX, offsetY, blur, spread, color) { - this.offsetX = offsetX; - this.offsetY = offsetY; - this.blur = blur; - this.spread = spread; - this.color = color; - } -} - -const _noShadows = []; - -function _computeShadowsForElevation(elevation, color) { - if (elevation <= 0.0) { - return _noShadows; - } else if (elevation <= 1.0) { - return _computeShadowElevation(2, color); - } else if (elevation <= 2.0) { - return _computeShadowElevation(4, color); - } else if (elevation <= 3.0) { - return _computeShadowElevation(6, color); - } else if (elevation <= 4.0) { - return _computeShadowElevation(8, color); - } else if (elevation <= 5.0) { - return _computeShadowElevation(16, color); - } else { - return _computeShadowElevation(24, color); - } -} - -function _computeShadowElevation(dp, color) { - // TODO(yjbanov): multiple shadows are very expensive. Find a more efficient - // method to render them. - let red = color[1]; - let green = color[2]; - let blue = color[3]; - - // let penumbraColor = `rgba(${red}, ${green}, ${blue}, 0.14)`; - // let ambientShadowColor = `rgba(${red}, ${green}, ${blue}, 0.12)`; - let umbraColor = `rgba(${red}, ${green}, ${blue}, 0.2)`; - - let result = []; - if (dp === 2) { - // result.push(new CanvasShadow(0.0, 2.0, 1.0, 0.0, penumbraColor)); - // result.push(new CanvasShadow(0.0, 3.0, 0.5, -2.0, ambientShadowColor)); - result.push(new CanvasShadow(0.0, 1.0, 2.5, 0.0, umbraColor)); - } else if (dp === 3) { - // result.push(new CanvasShadow(0.0, 1.5, 4.0, 0.0, penumbraColor)); - // result.push(new CanvasShadow(0.0, 3.0, 1.5, -2.0, ambientShadowColor)); - result.push(new CanvasShadow(0.0, 1.0, 4.0, 0.0, umbraColor)); - } else if (dp === 4) { - // result.push(new CanvasShadow(0.0, 4.0, 2.5, 0.0, penumbraColor)); - // result.push(new CanvasShadow(0.0, 1.0, 5.0, 0.0, ambientShadowColor)); - result.push(new CanvasShadow(0.0, 2.0, 2.0, -1.0, umbraColor)); - } else if (dp === 6) { - // result.push(new CanvasShadow(0.0, 6.0, 5.0, 0.0, penumbraColor)); - // result.push(new CanvasShadow(0.0, 1.0, 9.0, 0.0, ambientShadowColor)); - result.push(new CanvasShadow(0.0, 3.0, 2.5, -1.0, umbraColor)); - } else if (dp === 8) { - // result.push(new CanvasShadow(0.0, 4.0, 10.0, 1.0, penumbraColor)); - // result.push(new CanvasShadow(0.0, 3.0, 7.0, 2.0, ambientShadowColor)); - result.push(new CanvasShadow(0.0, 5.0, 2.5, -3.0, umbraColor)); - } else if (dp === 12) { - // result.push(new CanvasShadow(0.0, 12.0, 8.5, 2.0, penumbraColor)); - // result.push(new CanvasShadow(0.0, 5.0, 11.0, 4.0, ambientShadowColor)); - result.push(new CanvasShadow(0.0, 7.0, 4.0, -4.0, umbraColor)); - } else if (dp === 16) { - // result.push(new CanvasShadow(0.0, 16.0, 12.0, 2.0, penumbraColor)); - // result.push(new CanvasShadow(0.0, 6.0, 15.0, 5.0, ambientShadowColor)); - result.push(new CanvasShadow(0.0, 0.0, 5.0, -5.0, umbraColor)); - } else { - // result.push(new CanvasShadow(0.0, 24.0, 18.0, 3.0, penumbraColor)); - // result.push(new CanvasShadow(0.0, 9.0, 23.0, 8.0, ambientShadowColor)); - result.push(new CanvasShadow(0.0, 11.0, 7.5, -7.0, umbraColor)); - } - return result; -} - -const PathCommandType = { - moveTo: 0, - lineTo: 1, - ellipse: 2, - close: 3, - quadraticCurveTo: 4, - bezierCurveTo: 5, - rect: 6, - rrect: 7, -}; - -const TileMode = { - clamp: 0, - repeated: 1, -}; - -const BlurStyle = { - normal: 0, - solid: 1, - outer: 2, - inner: 3, -}; - -/// This makes the painter available as "background-image: paint(flt)". -registerPaint('flt', FlutterPainter); diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 0f2aa4e6c64850bf796746917c90b1307df87b19..4a5ddfe706a28952de8e5e216a5fe298ac77b0b0 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -64,7 +64,6 @@ part 'engine/dom_renderer.dart'; part 'engine/engine_canvas.dart'; part 'engine/frame_reference.dart'; part 'engine/history.dart'; -part 'engine/houdini_canvas.dart'; part 'engine/html_image_codec.dart'; part 'engine/keyboard.dart'; part 'engine/mouse_cursor.dart'; diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 0d9420c8f60c7e790f10562ea2db33245df85c94..1954fa5231363ca882a5701a9498096d0d8638b8 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -193,9 +193,9 @@ class BitmapCanvas extends EngineCanvas { /// /// See also: /// - /// * [PersistedStandardPicture._applyBitmapPaint] which uses this method to + /// * [PersistedPicture._applyBitmapPaint] which uses this method to /// decide whether to reuse this canvas or not. - /// * [PersistedStandardPicture._recycleCanvas] which also uses this method + /// * [PersistedPicture._recycleCanvas] which also uses this method /// for the same reason. bool isReusable() { return _devicePixelRatio == EngineWindow.browserDevicePixelRatio; diff --git a/lib/web_ui/lib/src/engine/color_filter.dart b/lib/web_ui/lib/src/engine/color_filter.dart index 0181695ca2d5a6fa31c5bd896edc5f283612929e..230cdf9a2ae25e75f36b7c930aae9ae78a02c1de 100644 --- a/lib/web_ui/lib/src/engine/color_filter.dart +++ b/lib/web_ui/lib/src/engine/color_filter.dart @@ -169,8 +169,4 @@ class EngineColorFilter implements ui.ColorFilter { return 'Unknown ColorFilter type. This is an error. If you\'re seeing this, please file an issue at https://github.com/flutter/flutter/issues/new.'; } } - - List webOnlySerializeToCssPaint() { - throw UnsupportedError('ColorFilter for CSS paint not yet supported'); - } } diff --git a/lib/web_ui/lib/src/engine/engine_canvas.dart b/lib/web_ui/lib/src/engine/engine_canvas.dart index 1e38b05360297bc0fb7a1cd1a96cc8510866cbb0..5b0f7d31d8167ad1db20b0414de040fc943bc15b 100644 --- a/lib/web_ui/lib/src/engine/engine_canvas.dart +++ b/lib/web_ui/lib/src/engine/engine_canvas.dart @@ -282,3 +282,127 @@ html.Element _drawParagraphElement( } 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 _elementStack = []; + + /// 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)); + } +} diff --git a/lib/web_ui/lib/src/engine/houdini_canvas.dart b/lib/web_ui/lib/src/engine/houdini_canvas.dart deleted file mode 100644 index 1f83140a69983018f38ce162489e727e5dafecb6..0000000000000000000000000000000000000000 --- a/lib/web_ui/lib/src/engine/houdini_canvas.dart +++ /dev/null @@ -1,370 +0,0 @@ -// 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. - -// TODO(yjbanov): optimization opportunities (see also houdini_painter.js) -// - collapse non-drawing paint operations -// - avoid producing DOM-based clips if there is no text -// - evaluate using stylesheets for static CSS properties -// - evaluate reusing houdini canvases - -// @dart = 2.10 -part of engine; - -/// A canvas that renders to a combination of HTML DOM and CSS Custom Paint API. -/// -/// This canvas produces paint commands for houdini_painter.js to apply. This -/// class must be kept in sync with houdini_painter.js. -class HoudiniCanvas extends EngineCanvas with SaveElementStackTracking { - @override - final html.Element rootElement = html.Element.tag('flt-houdini'); - - /// The rectangle positioned relative to the parent layer's coordinate system - /// where this canvas paints. - /// - /// Painting outside the bounds of this rectangle is cropped. - final ui.Rect? bounds; - - HoudiniCanvas(this.bounds) { - // TODO(yjbanov): would it be faster to specify static values in a - // stylesheet and let the browser apply them? - rootElement.style - ..position = 'absolute' - ..top = '0' - ..left = '0' - ..width = '${bounds!.size.width}px' - ..height = '${bounds!.size.height}px' - ..backgroundImage = 'paint(flt)'; - } - - /// Prepare to reuse this canvas by clearing it's current contents. - @override - void clear() { - super.clear(); - _serializedCommands = >[]; - // TODO(yjbanov): we should measure if reusing old elements is beneficial. - domRenderer.clearDom(rootElement); - } - - /// Paint commands serialized for sending to the CSS custom painter. - List> _serializedCommands = >[]; - - void apply(PaintCommand command) { - // Some commands are applied purely in HTML DOM and do not need to be - // serialized. - if (command is! PaintDrawParagraph && - command is! PaintDrawImageRect && - command is! PaintTransform) { - command.serializeToCssPaint(_serializedCommands); - } - command.apply(this); - } - - /// Sends the paint commands to the CSS custom painter for painting. - void commit() { - if (_serializedCommands.isNotEmpty) { - rootElement.style.setProperty('--flt', json.encode(_serializedCommands)); - } else { - rootElement.style.removeProperty('--flt'); - } - } - - @override - void clipRect(ui.Rect rect) { - final html.Element clip = html.Element.tag('flt-clip-rect'); - final String cssTransform = matrix4ToCssTransform( - transformWithOffset(currentTransform, ui.Offset(rect.left, rect.top))); - clip.style - ..overflow = 'hidden' - ..position = 'absolute' - ..transform = cssTransform - ..width = '${rect.width}px' - ..height = '${rect.height}px'; - - // The clipping element will translate the coordinate system as well, which - // is not what a clip should do. To offset that we translate in the opposite - // direction. - super.translate(-rect.left, -rect.top); - - currentElement.append(clip); - pushElement(clip); - } - - @override - void clipRRect(ui.RRect rrect) { - final ui.Rect outer = rrect.outerRect; - if (rrect.isRect) { - clipRect(outer); - return; - } - - final html.Element clip = html.Element.tag('flt-clip-rrect'); - final html.CssStyleDeclaration style = clip.style; - style - ..overflow = 'hidden' - ..position = 'absolute' - ..transform = 'translate(${outer.left}px, ${outer.right}px)' - ..width = '${outer.width}px' - ..height = '${outer.height}px'; - - if (rrect.tlRadiusY == rrect.tlRadiusX) { - style.borderTopLeftRadius = '${rrect.tlRadiusX}px'; - } else { - style.borderTopLeftRadius = '${rrect.tlRadiusX}px ${rrect.tlRadiusY}px'; - } - - if (rrect.trRadiusY == rrect.trRadiusX) { - style.borderTopRightRadius = '${rrect.trRadiusX}px'; - } else { - style.borderTopRightRadius = '${rrect.trRadiusX}px ${rrect.trRadiusY}px'; - } - - if (rrect.brRadiusY == rrect.brRadiusX) { - style.borderBottomRightRadius = '${rrect.brRadiusX}px'; - } else { - style.borderBottomRightRadius = - '${rrect.brRadiusX}px ${rrect.brRadiusY}px'; - } - - if (rrect.blRadiusY == rrect.blRadiusX) { - style.borderBottomLeftRadius = '${rrect.blRadiusX}px'; - } else { - style.borderBottomLeftRadius = - '${rrect.blRadiusX}px ${rrect.blRadiusY}px'; - } - - // The clipping element will translate the coordinate system as well, which - // is not what a clip should do. To offset that we translate in the opposite - // direction. - super.translate(-rrect.left, -rrect.top); - - currentElement.append(clip); - pushElement(clip); - } - - @override - void clipPath(ui.Path path) { - // TODO(yjbanov): implement. - } - - @override - void drawColor(ui.Color color, ui.BlendMode blendMode) { - // Drawn using CSS Paint. - } - - @override - void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaintData paint) { - // Drawn using CSS Paint. - } - - @override - void drawPaint(SurfacePaintData paint) { - // Drawn using CSS Paint. - } - - @override - void drawRect(ui.Rect rect, SurfacePaintData paint) { - // Drawn using CSS Paint. - } - - @override - void drawRRect(ui.RRect rrect, SurfacePaintData paint) { - // Drawn using CSS Paint. - } - - @override - void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaintData paint) { - // Drawn using CSS Paint. - } - - @override - void drawOval(ui.Rect rect, SurfacePaintData paint) { - // Drawn using CSS Paint. - } - - @override - void drawCircle(ui.Offset c, double radius, SurfacePaintData paint) { - // Drawn using CSS Paint. - } - - @override - void drawPath(ui.Path path, SurfacePaintData paint) { - // Drawn using CSS Paint. - } - - @override - void drawShadow(ui.Path path, ui.Color color, double elevation, - bool transparentOccluder) { - // Drawn using CSS Paint. - } - - @override - void drawImage(ui.Image image, ui.Offset p, SurfacePaintData paint) { - // TODO(yjbanov): implement. - } - - @override - void drawImageRect( - ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaintData paint) { - // TODO(yjbanov): implement src rectangle - final HtmlImage htmlImage = image as HtmlImage; - final html.Element imageBox = html.Element.tag('flt-img'); - final String cssTransform = matrix4ToCssTransform( - transformWithOffset(currentTransform, ui.Offset(dst.left, dst.top))); - imageBox.style - ..position = 'absolute' - ..transformOrigin = '0 0 0' - ..width = '${dst.width.toInt()}px' - ..height = '${dst.height.toInt()}px' - ..transform = cssTransform - ..backgroundImage = 'url(${htmlImage.imgElement.src})' - ..backgroundRepeat = 'norepeat' - ..backgroundSize = '${dst.width}px ${dst.height}px'; - currentElement.append(imageBox); - } - - @override - void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { - final html.Element paragraphElement = - _drawParagraphElement(paragraph as EngineParagraph, offset, transform: currentTransform); - currentElement.append(paragraphElement); - } - - @override - void drawVertices( - ui.Vertices vertices, ui.BlendMode blendMode, SurfacePaintData paint) { - // TODO(flutter_web): implement. - } - - @override - void drawPoints(ui.PointMode pointMode, Float32List points, SurfacePaintData paint) { - // TODO(flutter_web): implement. - } - - @override - void endOfPaint() {} -} - -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 _elementStack = []; - - /// 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)); - } -} diff --git a/lib/web_ui/lib/src/engine/rrect_renderer.dart b/lib/web_ui/lib/src/engine/rrect_renderer.dart index 767c0666c6649b251c583ceaf6b832bf53fd50fa..3ccc054331d4109a630ba8f6c0ef3864f26c4c68 100644 --- a/lib/web_ui/lib/src/engine/rrect_renderer.dart +++ b/lib/web_ui/lib/src/engine/rrect_renderer.dart @@ -7,7 +7,6 @@ part of engine; /// Renders an RRect using path primitives. abstract class _RRectRenderer { - // TODO(mdebbar): Backport the overlapping corners fix to houdini_painter.js // To draw the rounded rectangle, perform the following steps: // 0. Ensure border radius don't overlap // 1. Flip left,right top,bottom since web doesn't support flipped diff --git a/lib/web_ui/lib/src/engine/surface/path/path.dart b/lib/web_ui/lib/src/engine/surface/path/path.dart index 36b8e77be672a39cd641b6a5bd7a5f01ae0ae32f..33ece6bba1fc379e75169a8a213c226802416267 100644 --- a/lib/web_ui/lib/src/engine/surface/path/path.dart +++ b/lib/web_ui/lib/src/engine/surface/path/path.dart @@ -1542,12 +1542,6 @@ class SurfacePath implements ui.Path { ui.Rect? get webOnlyPathAsCircle => pathRef.isOval == -1 ? null : pathRef.getBounds(); - /// Serializes this path to a value that's sent to a CSS custom painter for - /// painting. - List webOnlySerializeToCssPaint() { - throw UnimplementedError(); - } - /// Returns if Path is empty. /// Empty Path may have FillType but has no points, verbs or weights. /// Constructor, reset and rewind makes SkPath empty. diff --git a/lib/web_ui/lib/src/engine/surface/picture.dart b/lib/web_ui/lib/src/engine/surface/picture.dart index 0249071a6fc93f4dfbb0ea66a458ab1d48e2ef6f..520a4378f1408357c11404b423ac9b76626431f7 100644 --- a/lib/web_ui/lib/src/engine/surface/picture.dart +++ b/lib/web_ui/lib/src/engine/surface/picture.dart @@ -71,292 +71,9 @@ void _recycleCanvas(EngineCanvas? canvas) { } } - -/// Signature of a function that instantiates a [PersistedPicture]. -typedef PersistedPictureFactory = PersistedPicture Function( - double dx, - double dy, - ui.Picture picture, - int hints, -); - -/// Function used by the [SceneBuilder] to instantiate a picture layer. -PersistedPictureFactory persistedPictureFactory = standardPictureFactory; - -/// Instantiates an implementation of a picture layer that uses DOM, CSS, and -/// 2D canvas for painting. -PersistedStandardPicture standardPictureFactory( - double dx, double dy, ui.Picture picture, int hints) { - return PersistedStandardPicture(dx, dy, picture, hints); -} - -/// Instantiates an implementation of a picture layer that uses CSS Paint API -/// (part of Houdini) for painting. -PersistedHoudiniPicture houdiniPictureFactory( - double dx, double dy, ui.Picture picture, int hints) { - return PersistedHoudiniPicture(dx, dy, picture, hints); -} - -class PersistedHoudiniPicture extends PersistedPicture { - PersistedHoudiniPicture(double dx, double dy, ui.Picture picture, int hints) - : super(dx, dy, picture as EnginePicture, hints) { - if (!_cssPainterRegistered) { - _registerCssPainter(); - } - } - - static bool _cssPainterRegistered = false; - - @override - double matchForUpdate(PersistedPicture existingSurface) { - // Houdini is display list-based so all pictures are cheap to repaint. - // However, if the picture hasn't changed at all then it's completely - // free. - return existingSurface.picture == picture ? 0.0 : 1.0; - } - - static void _registerCssPainter() { - _cssPainterRegistered = true; - final dynamic css = js_util.getProperty(html.window, 'CSS'); - final dynamic paintWorklet = js_util.getProperty(css, 'paintWorklet'); - if (paintWorklet == null) { - html.window.console.warn( - 'WARNING: CSS.paintWorklet not available. Paint worklets are only ' - 'supported on sites served from https:// or http://localhost.'); - return; - } - js_util.callMethod( - paintWorklet, - 'addModule', - [ - '/packages/flutter_web_ui/assets/houdini_painter.js', - ], - ); - } - - /// Houdini does not paint to bitmap. - @override - int get bitmapPixelCount => 0; - - @override - void applyPaint(EngineCanvas? oldCanvas) { - _recycleCanvas(oldCanvas); - final HoudiniCanvas canvas = HoudiniCanvas(_optimalLocalCullRect); - _canvas = canvas; - domRenderer.clearDom(rootElement!); - rootElement!.append(_canvas!.rootElement); - picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect); - canvas.commit(); - } -} - -class PersistedStandardPicture extends PersistedPicture { - PersistedStandardPicture(double dx, double dy, ui.Picture picture, int hints) - : super(dx, dy, picture as EnginePicture, hints); - - @override - double matchForUpdate(PersistedStandardPicture existingSurface) { - if (existingSurface.picture == picture) { - // Picture is the same, return perfect score. - return 0.0; - } - - if (!existingSurface.picture.recordingCanvas!.didDraw) { - // The previous surface didn't draw anything and therefore has no - // resources to reuse. - return 1.0; - } - - final bool didRequireBitmap = - existingSurface.picture.recordingCanvas!.hasArbitraryPaint; - final bool requiresBitmap = picture.recordingCanvas!.hasArbitraryPaint; - if (didRequireBitmap != requiresBitmap) { - // Switching canvas types is always expensive. - return 1.0; - } else if (!requiresBitmap) { - // Currently DomCanvas is always expensive to repaint, as we always throw - // out all the DOM we rendered before. This may change in the future, at - // which point we may return other values here. - return 1.0; - } else { - final BitmapCanvas? oldCanvas = existingSurface._canvas as BitmapCanvas?; - if (oldCanvas == null) { - // We did not allocate a canvas last time. This can happen when the - // picture is completely clipped out of the view. - return 1.0; - } else if (!oldCanvas.doesFitBounds(_exactLocalCullRect!)) { - // The canvas needs to be resized before painting. - return 1.0; - } else { - final int newPixelCount = BitmapCanvas._widthToPhysical(_exactLocalCullRect!.width) - * BitmapCanvas._heightToPhysical(_exactLocalCullRect!.height); - final int oldPixelCount = - oldCanvas._widthInBitmapPixels * oldCanvas._heightInBitmapPixels; - - if (oldPixelCount == 0) { - return 1.0; - } - - final double pixelCountRatio = newPixelCount / oldPixelCount; - assert(0 <= pixelCountRatio && pixelCountRatio <= 1.0, - 'Invalid pixel count ratio $pixelCountRatio'); - return 1.0 - pixelCountRatio; - } - } - } - - @override - Matrix4? get localTransformInverse => null; - - @override - int get bitmapPixelCount { - if (_canvas is! BitmapCanvas) { - return 0; - } - - final BitmapCanvas bitmapCanvas = _canvas as BitmapCanvas; - return bitmapCanvas.bitmapPixelCount; - } - - @override - void applyPaint(EngineCanvas? oldCanvas) { - if (picture.recordingCanvas!.hasArbitraryPaint) { - _applyBitmapPaint(oldCanvas); - } else { - _applyDomPaint(oldCanvas); - } - } - - void _applyDomPaint(EngineCanvas? oldCanvas) { - _recycleCanvas(oldCanvas); - _canvas = DomCanvas(); - domRenderer.clearDom(rootElement!); - rootElement!.append(_canvas!.rootElement); - picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect); - } - - void _applyBitmapPaint(EngineCanvas? oldCanvas) { - if (oldCanvas is BitmapCanvas && - oldCanvas.doesFitBounds(_optimalLocalCullRect!) && - oldCanvas.isReusable()) { - if (_debugShowCanvasReuseStats) { - DebugCanvasReuseOverlay.instance.keptCount++; - } - oldCanvas.bounds = _optimalLocalCullRect!; - _canvas = oldCanvas; - oldCanvas.setElementCache(_elementCache); - _canvas!.clear(); - picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect); - } else { - // We can't use the old canvas because the size has changed, so we put - // it in a cache for later reuse. - _recycleCanvas(oldCanvas); - // We cannot paint immediately because not all canvases that we may be - // able to reuse have been released yet. So instead we enqueue this - // picture to be painted after the update cycle is done syncing the layer - // tree then reuse canvases that were freed up. - _paintQueue.add(_PaintRequest( - canvasSize: _optimalLocalCullRect!.size, - paintCallback: () { - _canvas = _findOrCreateCanvas(_optimalLocalCullRect!); - assert(_canvas is BitmapCanvas - && (_canvas as BitmapCanvas?)!._elementCache == _elementCache); - if (_debugExplainSurfaceStats) { - final BitmapCanvas bitmapCanvas = _canvas as BitmapCanvas; - _surfaceStatsFor(this).paintPixelCount += - bitmapCanvas.bitmapPixelCount; - } - domRenderer.clearDom(rootElement!); - rootElement!.append(_canvas!.rootElement); - _canvas!.clear(); - picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect); - }, - )); - } - } - - /// Attempts to reuse a canvas from the [_recycledCanvases]. Allocates a new - /// one if unable to reuse. - /// - /// The best recycled canvas is one that: - /// - /// - Fits the requested [canvasSize]. This is a hard requirement. Otherwise - /// we risk clipping the picture. - /// - Is the smallest among all possible reusable canvases. This makes canvas - /// reuse more efficient. - /// - Contains no more than twice the number of requested pixels. This makes - /// sure we do not use too much memory for small canvases. - BitmapCanvas _findOrCreateCanvas(ui.Rect bounds) { - final ui.Size canvasSize = bounds.size; - BitmapCanvas? bestRecycledCanvas; - double lastPixelCount = double.infinity; - for (int i = 0; i < _recycledCanvases.length; i++) { - final BitmapCanvas candidate = _recycledCanvases[i]; - if (!candidate.isReusable()) { - continue; - } - - final ui.Size candidateSize = candidate.size; - final double candidatePixelCount = - candidateSize.width * candidateSize.height; - - final bool fits = candidate.doesFitBounds(bounds); - final bool isSmaller = candidatePixelCount < lastPixelCount; - if (fits && isSmaller) { - // [isTooSmall] is used to make sure that a small picture doesn't - // reuse and hold onto memory of a large canvas. - final double requestedPixelCount = bounds.width * bounds.height; - final bool isTooSmall = isSmaller && - requestedPixelCount > 1 && - (candidatePixelCount / requestedPixelCount) > 4; - if (!isTooSmall) { - bestRecycledCanvas = candidate; - lastPixelCount = candidatePixelCount; - final bool fitsExactly = candidateSize.width == canvasSize.width && - candidateSize.height == canvasSize.height; - if (fitsExactly) { - // No need to keep looking any more. - break; - } - } - } - } - - if (bestRecycledCanvas != null) { - if (_debugExplainSurfaceStats) { - _surfaceStatsFor(this).reuseCanvasCount++; - } - _recycledCanvases.remove(bestRecycledCanvas); - if (_debugShowCanvasReuseStats) { - DebugCanvasReuseOverlay.instance.inRecycleCount = - _recycledCanvases.length; - } - if (_debugShowCanvasReuseStats) { - DebugCanvasReuseOverlay.instance.reusedCount++; - } - bestRecycledCanvas.bounds = bounds; - bestRecycledCanvas.setElementCache(_elementCache); - return bestRecycledCanvas; - } - - if (_debugShowCanvasReuseStats) { - DebugCanvasReuseOverlay.instance.createdCount++; - } - final BitmapCanvas canvas = BitmapCanvas(bounds); - canvas.setElementCache(_elementCache); - if (_debugExplainSurfaceStats) { - _surfaceStatsFor(this) - ..allocateBitmapCanvasCount += 1 - ..allocatedBitmapSizeInPixels = - canvas._widthInBitmapPixels * canvas._heightInBitmapPixels; - } - return canvas; - } -} - /// A surface that uses a combination of ``, `
` and `

` elements /// to draw shapes and text. -abstract class PersistedPicture extends PersistedLeafSurface { +class PersistedPicture extends PersistedLeafSurface { PersistedPicture(this.dx, this.dy, this.picture, this.hints) : localPaintBounds = picture.recordingCanvas!.pictureBounds; @@ -553,7 +270,14 @@ abstract class PersistedPicture extends PersistedLeafSurface { /// /// If the implementation does not paint onto a bitmap canvas, it should /// return zero. - int get bitmapPixelCount; + int get bitmapPixelCount { + if (_canvas is! BitmapCanvas) { + return 0; + } + + final BitmapCanvas bitmapCanvas = _canvas as BitmapCanvas; + return bitmapCanvas.bitmapPixelCount; + } void _applyPaint(PersistedPicture? oldSurface) { final EngineCanvas? oldCanvas = oldSurface?._canvas; @@ -575,8 +299,193 @@ abstract class PersistedPicture extends PersistedLeafSurface { applyPaint(oldCanvas); } - /// Concrete implementations implement this method to do actual painting. - void applyPaint(EngineCanvas? oldCanvas); + @override + double matchForUpdate(PersistedPicture existingSurface) { + if (existingSurface.picture == picture) { + // Picture is the same, return perfect score. + return 0.0; + } + + if (!existingSurface.picture.recordingCanvas!.didDraw) { + // The previous surface didn't draw anything and therefore has no + // resources to reuse. + return 1.0; + } + + final bool didRequireBitmap = + existingSurface.picture.recordingCanvas!.hasArbitraryPaint; + final bool requiresBitmap = picture.recordingCanvas!.hasArbitraryPaint; + if (didRequireBitmap != requiresBitmap) { + // Switching canvas types is always expensive. + return 1.0; + } else if (!requiresBitmap) { + // Currently DomCanvas is always expensive to repaint, as we always throw + // out all the DOM we rendered before. This may change in the future, at + // which point we may return other values here. + return 1.0; + } else { + final BitmapCanvas? oldCanvas = existingSurface._canvas as BitmapCanvas?; + if (oldCanvas == null) { + // We did not allocate a canvas last time. This can happen when the + // picture is completely clipped out of the view. + return 1.0; + } else if (!oldCanvas.doesFitBounds(_exactLocalCullRect!)) { + // The canvas needs to be resized before painting. + return 1.0; + } else { + final int newPixelCount = BitmapCanvas._widthToPhysical(_exactLocalCullRect!.width) + * BitmapCanvas._heightToPhysical(_exactLocalCullRect!.height); + final int oldPixelCount = + oldCanvas._widthInBitmapPixels * oldCanvas._heightInBitmapPixels; + + if (oldPixelCount == 0) { + return 1.0; + } + + final double pixelCountRatio = newPixelCount / oldPixelCount; + assert(0 <= pixelCountRatio && pixelCountRatio <= 1.0, + 'Invalid pixel count ratio $pixelCountRatio'); + return 1.0 - pixelCountRatio; + } + } + } + + @override + Matrix4? get localTransformInverse => null; + + void applyPaint(EngineCanvas? oldCanvas) { + if (picture.recordingCanvas!.hasArbitraryPaint) { + _applyBitmapPaint(oldCanvas); + } else { + _applyDomPaint(oldCanvas); + } + } + + void _applyDomPaint(EngineCanvas? oldCanvas) { + _recycleCanvas(oldCanvas); + _canvas = DomCanvas(); + domRenderer.clearDom(rootElement!); + rootElement!.append(_canvas!.rootElement); + picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect); + } + + void _applyBitmapPaint(EngineCanvas? oldCanvas) { + if (oldCanvas is BitmapCanvas && + oldCanvas.doesFitBounds(_optimalLocalCullRect!) && + oldCanvas.isReusable()) { + if (_debugShowCanvasReuseStats) { + DebugCanvasReuseOverlay.instance.keptCount++; + } + oldCanvas.bounds = _optimalLocalCullRect!; + _canvas = oldCanvas; + oldCanvas.setElementCache(_elementCache); + _canvas!.clear(); + picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect); + } else { + // We can't use the old canvas because the size has changed, so we put + // it in a cache for later reuse. + _recycleCanvas(oldCanvas); + // We cannot paint immediately because not all canvases that we may be + // able to reuse have been released yet. So instead we enqueue this + // picture to be painted after the update cycle is done syncing the layer + // tree then reuse canvases that were freed up. + _paintQueue.add(_PaintRequest( + canvasSize: _optimalLocalCullRect!.size, + paintCallback: () { + _canvas = _findOrCreateCanvas(_optimalLocalCullRect!); + assert(_canvas is BitmapCanvas + && (_canvas as BitmapCanvas?)!._elementCache == _elementCache); + if (_debugExplainSurfaceStats) { + final BitmapCanvas bitmapCanvas = _canvas as BitmapCanvas; + _surfaceStatsFor(this).paintPixelCount += + bitmapCanvas.bitmapPixelCount; + } + domRenderer.clearDom(rootElement!); + rootElement!.append(_canvas!.rootElement); + _canvas!.clear(); + picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect); + }, + )); + } + } + + /// Attempts to reuse a canvas from the [_recycledCanvases]. Allocates a new + /// one if unable to reuse. + /// + /// The best recycled canvas is one that: + /// + /// - Fits the requested [canvasSize]. This is a hard requirement. Otherwise + /// we risk clipping the picture. + /// - Is the smallest among all possible reusable canvases. This makes canvas + /// reuse more efficient. + /// - Contains no more than twice the number of requested pixels. This makes + /// sure we do not use too much memory for small canvases. + BitmapCanvas _findOrCreateCanvas(ui.Rect bounds) { + final ui.Size canvasSize = bounds.size; + BitmapCanvas? bestRecycledCanvas; + double lastPixelCount = double.infinity; + for (int i = 0; i < _recycledCanvases.length; i++) { + final BitmapCanvas candidate = _recycledCanvases[i]; + if (!candidate.isReusable()) { + continue; + } + + final ui.Size candidateSize = candidate.size; + final double candidatePixelCount = + candidateSize.width * candidateSize.height; + + final bool fits = candidate.doesFitBounds(bounds); + final bool isSmaller = candidatePixelCount < lastPixelCount; + if (fits && isSmaller) { + // [isTooSmall] is used to make sure that a small picture doesn't + // reuse and hold onto memory of a large canvas. + final double requestedPixelCount = bounds.width * bounds.height; + final bool isTooSmall = isSmaller && + requestedPixelCount > 1 && + (candidatePixelCount / requestedPixelCount) > 4; + if (!isTooSmall) { + bestRecycledCanvas = candidate; + lastPixelCount = candidatePixelCount; + final bool fitsExactly = candidateSize.width == canvasSize.width && + candidateSize.height == canvasSize.height; + if (fitsExactly) { + // No need to keep looking any more. + break; + } + } + } + } + + if (bestRecycledCanvas != null) { + if (_debugExplainSurfaceStats) { + _surfaceStatsFor(this).reuseCanvasCount++; + } + _recycledCanvases.remove(bestRecycledCanvas); + if (_debugShowCanvasReuseStats) { + DebugCanvasReuseOverlay.instance.inRecycleCount = + _recycledCanvases.length; + } + if (_debugShowCanvasReuseStats) { + DebugCanvasReuseOverlay.instance.reusedCount++; + } + bestRecycledCanvas.bounds = bounds; + bestRecycledCanvas.setElementCache(_elementCache); + return bestRecycledCanvas; + } + + if (_debugShowCanvasReuseStats) { + DebugCanvasReuseOverlay.instance.createdCount++; + } + final BitmapCanvas canvas = BitmapCanvas(bounds); + canvas.setElementCache(_elementCache); + if (_debugExplainSurfaceStats) { + _surfaceStatsFor(this) + ..allocateBitmapCanvasCount += 1 + ..allocatedBitmapSizeInPixels = + canvas._widthInBitmapPixels * canvas._heightInBitmapPixels; + } + return canvas; + } void _applyTranslate() { rootElement!.style.transform = 'translate(${dx}px, ${dy}px)'; diff --git a/lib/web_ui/lib/src/engine/surface/recording_canvas.dart b/lib/web_ui/lib/src/engine/surface/recording_canvas.dart index c9288b7aca27b7d661e379fd26fc3f149cce0a7b..a7c994990ad5d5a2ac527d0c7237b80bf4f47201 100644 --- a/lib/web_ui/lib/src/engine/surface/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/surface/recording_canvas.dart @@ -608,8 +608,6 @@ abstract class PaintCommand { const PaintCommand(); void apply(EngineCanvas? canvas); - - void serializeToCssPaint(List> serializedCommands); } /// A [PaintCommand] that affect pixels on the screen (unlike, for example, the @@ -665,11 +663,6 @@ class PaintSave extends PaintCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add(const [1]); - } } class PaintRestore extends PaintCommand { @@ -688,11 +681,6 @@ class PaintRestore extends PaintCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add(const [2]); - } } class PaintTranslate extends PaintCommand { @@ -714,11 +702,6 @@ class PaintTranslate extends PaintCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([3, dx, dy]); - } } class PaintScale extends PaintCommand { @@ -740,11 +723,6 @@ class PaintScale extends PaintCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([4, sx, sy]); - } } class PaintRotate extends PaintCommand { @@ -765,11 +743,6 @@ class PaintRotate extends PaintCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([5, radians]); - } } class PaintTransform extends PaintCommand { @@ -790,11 +763,6 @@ class PaintTransform extends PaintCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([6]..addAll(matrix4)); - } } class PaintSkew extends PaintCommand { @@ -816,11 +784,6 @@ class PaintSkew extends PaintCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([7, sx, sy]); - } } class PaintClipRect extends DrawCommand { @@ -841,11 +804,6 @@ class PaintClipRect extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([8, _serializeRectToCssPaint(rect)]); - } } class PaintClipRRect extends DrawCommand { @@ -866,14 +824,6 @@ class PaintClipRRect extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([ - 9, - _serializeRRectToCssPaint(rrect), - ]); - } } class PaintClipPath extends DrawCommand { @@ -894,11 +844,6 @@ class PaintClipPath extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([10, path.webOnlySerializeToCssPaint()]); - } } class PaintDrawColor extends DrawCommand { @@ -920,12 +865,6 @@ class PaintDrawColor extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands - .add([11, colorToCssString(color), blendMode.index]); - } } class PaintDrawLine extends DrawCommand { @@ -948,18 +887,6 @@ class PaintDrawLine extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([ - 12, - p1.dx, - p1.dy, - p2.dx, - p2.dy, - _serializePaintToCssPaint(paint) - ]); - } } class PaintDrawPaint extends DrawCommand { @@ -980,11 +907,6 @@ class PaintDrawPaint extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([13, _serializePaintToCssPaint(paint)]); - } } class PaintDrawVertices extends DrawCommand { @@ -1006,11 +928,6 @@ class PaintDrawVertices extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - throw UnimplementedError(); - } } class PaintDrawPoints extends DrawCommand { @@ -1032,11 +949,6 @@ class PaintDrawPoints extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - throw UnimplementedError(); - } } class PaintDrawRect extends DrawCommand { @@ -1058,15 +970,6 @@ class PaintDrawRect extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([ - 14, - _serializeRectToCssPaint(rect), - _serializePaintToCssPaint(paint) - ]); - } } class PaintDrawRRect extends DrawCommand { @@ -1088,15 +991,6 @@ class PaintDrawRRect extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([ - 15, - _serializeRRectToCssPaint(rrect), - _serializePaintToCssPaint(paint), - ]); - } } class PaintDrawDRRect extends DrawCommand { @@ -1125,16 +1019,6 @@ class PaintDrawDRRect extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([ - 16, - _serializeRRectToCssPaint(outer), - _serializeRRectToCssPaint(inner), - _serializePaintToCssPaint(paint), - ]); - } } class PaintDrawOval extends DrawCommand { @@ -1156,15 +1040,6 @@ class PaintDrawOval extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([ - 17, - _serializeRectToCssPaint(rect), - _serializePaintToCssPaint(paint), - ]); - } } class PaintDrawCircle extends DrawCommand { @@ -1187,17 +1062,6 @@ class PaintDrawCircle extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([ - 18, - c.dx, - c.dy, - radius, - _serializePaintToCssPaint(paint), - ]); - } } class PaintDrawPath extends DrawCommand { @@ -1219,15 +1083,6 @@ class PaintDrawPath extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([ - 19, - path.webOnlySerializeToCssPaint(), - _serializePaintToCssPaint(paint), - ]); - } } class PaintDrawShadow extends DrawCommand { @@ -1252,22 +1107,6 @@ class PaintDrawShadow extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - serializedCommands.add([ - 20, - path.webOnlySerializeToCssPaint(), - [ - color.alpha, - color.red, - color.green, - color.blue, - ], - elevation, - transparentOccluder, - ]); - } } class PaintDrawImage extends DrawCommand { @@ -1290,13 +1129,6 @@ class PaintDrawImage extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - if (assertionsEnabled) { - throw UnsupportedError('drawImage not serializable'); - } - } } class PaintDrawImageRect extends DrawCommand { @@ -1320,13 +1152,6 @@ class PaintDrawImageRect extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - if (assertionsEnabled) { - throw UnsupportedError('drawImageRect not serializable'); - } - } } class PaintDrawParagraph extends DrawCommand { @@ -1348,55 +1173,6 @@ class PaintDrawParagraph extends DrawCommand { return super.toString(); } } - - @override - void serializeToCssPaint(List> serializedCommands) { - if (assertionsEnabled) { - throw UnsupportedError('drawParagraph not serializable'); - } - } -} - -List _serializePaintToCssPaint(SurfacePaintData paint) { - final EngineGradient? engineShader = paint.shader as EngineGradient?; - return [ - paint.blendMode?.index, - paint.style?.index, - paint.strokeWidth, - paint.strokeCap?.index, - paint.isAntiAlias, - colorToCssString(paint.color), - engineShader?.webOnlySerializeToCssPaint(), - paint.maskFilter?.webOnlySerializeToCssPaint(), - paint.filterQuality?.index, - paint.colorFilter?.webOnlySerializeToCssPaint(), - ]; -} - -List _serializeRectToCssPaint(ui.Rect rect) { - return [ - rect.left, - rect.top, - rect.right, - rect.bottom, - ]; -} - -List _serializeRRectToCssPaint(ui.RRect rrect) { - return [ - rrect.left, - rrect.top, - rrect.right, - rrect.bottom, - rrect.tlRadiusX, - rrect.tlRadiusY, - rrect.trRadiusX, - rrect.trRadiusY, - rrect.brRadiusX, - rrect.brRadiusY, - rrect.blRadiusX, - rrect.blRadiusY, - ]; } class Subpath { @@ -1421,14 +1197,6 @@ class Subpath { return result; } - List serializeToCssPaint() { - final List serialization = []; - for (int i = 0; i < commands.length; i++) { - serialization.add(commands[i].serializeToCssPaint()); - } - return serialization; - } - @override String toString() { if (assertionsEnabled) { @@ -1439,26 +1207,11 @@ class Subpath { } } -/// ! Houdini implementation relies on indices here. Keep in sync. -class PathCommandTypes { - static const int moveTo = 0; - static const int lineTo = 1; - static const int ellipse = 2; - static const int close = 3; - static const int quadraticCurveTo = 4; - static const int bezierCurveTo = 5; - static const int rect = 6; - static const int rRect = 7; -} - abstract class PathCommand { - final int type; - const PathCommand(this.type); + const PathCommand(); PathCommand shifted(ui.Offset offset); - List serializeToCssPaint(); - /// Transform the command and add to targetPath. void transform(Float32List matrix4, SurfacePath targetPath); @@ -1472,18 +1225,13 @@ class MoveTo extends PathCommand { final double x; final double y; - const MoveTo(this.x, this.y) : super(PathCommandTypes.moveTo); + const MoveTo(this.x, this.y); @override MoveTo shifted(ui.Offset offset) { return MoveTo(x + offset.dx, y + offset.dy); } - @override - List serializeToCssPaint() { - return [1, x, y]; - } - @override void transform(Float32List matrix4, ui.Path targetPath) { final ui.Offset offset = PathCommand._transformOffset(x, y, matrix4); @@ -1504,18 +1252,13 @@ class LineTo extends PathCommand { final double x; final double y; - const LineTo(this.x, this.y) : super(PathCommandTypes.lineTo); + const LineTo(this.x, this.y); @override LineTo shifted(ui.Offset offset) { return LineTo(x + offset.dx, y + offset.dy); } - @override - List serializeToCssPaint() { - return [2, x, y]; - } - @override void transform(Float32List matrix4, ui.Path targetPath) { final ui.Offset offset = PathCommand._transformOffset(x, y, matrix4); @@ -1543,8 +1286,7 @@ class Ellipse extends PathCommand { final bool anticlockwise; const Ellipse(this.x, this.y, this.radiusX, this.radiusY, this.rotation, - this.startAngle, this.endAngle, this.anticlockwise) - : super(PathCommandTypes.ellipse); + this.startAngle, this.endAngle, this.anticlockwise); @override Ellipse shifted(ui.Offset offset) { @@ -1552,21 +1294,6 @@ class Ellipse extends PathCommand { startAngle, endAngle, anticlockwise); } - @override - List serializeToCssPaint() { - return [ - 3, - x, - y, - radiusX, - radiusY, - rotation, - startAngle, - endAngle, - anticlockwise, - ]; - } - @override void transform(Float32List matrix4, SurfacePath targetPath) { final ui.Path bezierPath = ui.Path(); @@ -1686,8 +1413,7 @@ class QuadraticCurveTo extends PathCommand { final double x2; final double y2; - const QuadraticCurveTo(this.x1, this.y1, this.x2, this.y2) - : super(PathCommandTypes.quadraticCurveTo); + const QuadraticCurveTo(this.x1, this.y1, this.x2, this.y2); @override QuadraticCurveTo shifted(ui.Offset offset) { @@ -1695,11 +1421,6 @@ class QuadraticCurveTo extends PathCommand { x1 + offset.dx, y1 + offset.dy, x2 + offset.dx, y2 + offset.dy); } - @override - List serializeToCssPaint() { - return [4, x1, y1, x2, y2]; - } - @override void transform(Float32List matrix4, ui.Path targetPath) { final double m0 = matrix4[0]; @@ -1734,8 +1455,7 @@ class BezierCurveTo extends PathCommand { final double x3; final double y3; - const BezierCurveTo(this.x1, this.y1, this.x2, this.y2, this.x3, this.y3) - : super(PathCommandTypes.bezierCurveTo); + const BezierCurveTo(this.x1, this.y1, this.x2, this.y2, this.x3, this.y3); @override BezierCurveTo shifted(ui.Offset offset) { @@ -1743,11 +1463,6 @@ class BezierCurveTo extends PathCommand { y2 + offset.dy, x3 + offset.dx, y3 + offset.dy); } - @override - List serializeToCssPaint() { - return [5, x1, y1, x2, y2, x3, y3]; - } - @override void transform(Float32List matrix4, ui.Path targetPath) { final double s0 = matrix4[0]; @@ -1782,8 +1497,7 @@ class RectCommand extends PathCommand { final double width; final double height; - const RectCommand(this.x, this.y, this.width, this.height) - : super(PathCommandTypes.rect); + const RectCommand(this.x, this.y, this.width, this.height); @override RectCommand shifted(ui.Offset offset) { @@ -1824,11 +1538,6 @@ class RectCommand extends PathCommand { } } - @override - List serializeToCssPaint() { - return [6, x, y, width, height]; - } - @override String toString() { if (assertionsEnabled) { @@ -1842,18 +1551,13 @@ class RectCommand extends PathCommand { class RRectCommand extends PathCommand { final ui.RRect rrect; - const RRectCommand(this.rrect) : super(PathCommandTypes.rRect); + const RRectCommand(this.rrect); @override RRectCommand shifted(ui.Offset offset) { return RRectCommand(rrect.shift(offset)); } - @override - List serializeToCssPaint() { - return [7, _serializeRRectToCssPaint(rrect)]; - } - @override void transform(Float32List matrix4, SurfacePath targetPath) { final ui.Path roundRectPath = ui.Path(); @@ -1872,18 +1576,11 @@ class RRectCommand extends PathCommand { } class CloseCommand extends PathCommand { - const CloseCommand() : super(PathCommandTypes.close); - @override CloseCommand shifted(ui.Offset offset) { return this; } - @override - List serializeToCssPaint() { - return [8]; - } - @override void transform(Float32List matrix4, ui.Path targetPath) { targetPath.close(); diff --git a/lib/web_ui/lib/src/engine/surface/scene_builder.dart b/lib/web_ui/lib/src/engine/surface/scene_builder.dart index fd38e403e38b8a4a3635fc4af467e6239db82176..799e0b0950a2fc8222d3071860a31984cc0dc918 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -363,7 +363,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { if (willChangeHint) { hints |= 2; } - _addSurface(persistedPictureFactory(offset.dx, offset.dy, picture, hints)); + _addSurface(PersistedPicture(offset.dx, offset.dy, picture as EnginePicture, hints)); } /// Adds a backend texture to the scene. diff --git a/lib/web_ui/lib/src/engine/surface/shader.dart b/lib/web_ui/lib/src/engine/surface/shader.dart index 6fb21b6f70b675fb44abc6e9d7b530d4f8d22dbe..4dae88f674bf9d6b470ad5d0fb84ffd5cb0be16e 100644 --- a/lib/web_ui/lib/src/engine/surface/shader.dart +++ b/lib/web_ui/lib/src/engine/surface/shader.dart @@ -11,10 +11,6 @@ abstract class EngineGradient implements ui.Gradient { /// Creates a fill style to be used in painting. Object createPaintStyle(html.CanvasRenderingContext2D? ctx); - - List webOnlySerializeToCssPaint() { - throw UnsupportedError('CSS paint not implemented for this shader type'); - } } class GradientSweep extends EngineGradient { @@ -100,24 +96,6 @@ class GradientLinear extends EngineGradient { } return gradient; } - - @override - List webOnlySerializeToCssPaint() { - final List serializedColors = []; - for (int i = 0; i < colors.length; i++) { - serializedColors.add(colorToCssString(colors[i])); - } - return [ - 1, - from.dx, - from.dy, - to.dx, - to.dy, - serializedColors, - colorStops, - tileMode.index - ]; - } } // TODO(flutter_web): For transforms and tile modes implement as webgl diff --git a/lib/web_ui/lib/src/engine/surface/surface_stats.dart b/lib/web_ui/lib/src/engine/surface/surface_stats.dart index 2fc0487f1a123853799d0e83bcba1a6f2a2809e5..34f4595d260c95994daf3b0ac3d7dfba033928d0 100644 --- a/lib/web_ui/lib/src/engine/surface/surface_stats.dart +++ b/lib/web_ui/lib/src/engine/surface/surface_stats.dart @@ -237,7 +237,7 @@ void _debugPrintSurfaceStats(PersistedScene scene, int frameNumber) { elementReuseCount += stats.reuseElementCount; totalAllocatedDomNodeCount += stats.allocatedDomNodeCount; - if (surface is PersistedStandardPicture) { + if (surface is PersistedPicture) { pictureCount += 1; paintCount += stats.paintCount; diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index 0760a1a6d2e479433cbe55d215b48fd2d89d7428..9f46e337df0afe13cda8f8553c76faecfe83fd57 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -1323,10 +1323,6 @@ abstract class ColorFilter { /// to the RGB channels. const factory ColorFilter.srgbToLinearGamma() = engine.EngineColorFilter.srgbToLinearGamma; - - List webOnlySerializeToCssPaint() { - throw UnsupportedError('ColorFilter for CSS paint not yet supported'); - } } /// Styles to use for blurs in [MaskFilter] objects. @@ -1402,10 +1398,6 @@ class MaskFilter { @override int get hashCode => hashValues(_style, _sigma); - List webOnlySerializeToCssPaint() { - return [_style.index, _sigma]; - } - @override String toString() => 'MaskFilter.blur($_style, ${_sigma.toStringAsFixed(1)})'; } diff --git a/lib/web_ui/test/canvas_test.dart b/lib/web_ui/test/canvas_test.dart index 7ce7143150451e135279bf00e22baefbed424195..609f848695218b299ce647ae58ef0ed9674607f7 100644 --- a/lib/web_ui/test/canvas_test.dart +++ b/lib/web_ui/test/canvas_test.dart @@ -26,7 +26,6 @@ void main() { test(description, () { testFn(BitmapCanvas(canvasSize)); testFn(DomCanvas()); - testFn(HoudiniCanvas(canvasSize)); testFn(mockCanvas = MockEngineCanvas()); if (whenDone != null) { whenDone(); diff --git a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart index 2c270a0a0f93843fbf2292d59f58b3d1d4103083..5a8616f02a8035a6b6215eae9a466160ffca6256 100644 --- a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart @@ -133,7 +133,7 @@ void _testCullRectComputation() { }); builder.build(); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect(picture.optimalLocalCullRect, const Rect.fromLTRB(0, 0, 500, 100)); }, skip: '''TODO(https://github.com/flutter/flutter/issues/40395) Needs ability to set iframe to 500,100 size. Current screen seems to be 500,500'''); @@ -147,7 +147,7 @@ void _testCullRectComputation() { }); builder.build(); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect(picture.optimalLocalCullRect, const Rect.fromLTRB(0, 0, 20, 20)); }); @@ -161,7 +161,7 @@ void _testCullRectComputation() { }); builder.build(); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect(picture.optimalLocalCullRect, Rect.zero); expect(picture.debugExactGlobalCullRect, Rect.zero); }); @@ -176,7 +176,7 @@ void _testCullRectComputation() { }); builder.build(); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect(picture.optimalLocalCullRect, const Rect.fromLTRB(40, 40, 60, 60)); }); @@ -195,7 +195,7 @@ void _testCullRectComputation() { builder.build(); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect(picture.optimalLocalCullRect, const Rect.fromLTRB(40, 40, 60, 60)); }); @@ -213,7 +213,7 @@ void _testCullRectComputation() { builder.build(); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect( picture.debugExactGlobalCullRect, const Rect.fromLTRB(0, 70, 20, 100)); expect(picture.optimalLocalCullRect, const Rect.fromLTRB(0, -20, 20, 10)); @@ -244,7 +244,7 @@ void _testCullRectComputation() { await matchGoldenFile('compositing_cull_rect_fills_layer_clip.png', region: region); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect(picture.optimalLocalCullRect, const Rect.fromLTRB(40, 40, 70, 70)); }); @@ -274,7 +274,7 @@ void _testCullRectComputation() { 'compositing_cull_rect_intersects_clip_and_paint_bounds.png', region: region); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect(picture.optimalLocalCullRect, const Rect.fromLTRB(50, 40, 70, 70)); }); @@ -305,7 +305,7 @@ void _testCullRectComputation() { await matchGoldenFile('compositing_cull_rect_offset_inside_layer_clip.png', region: region); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect(picture.optimalLocalCullRect, const Rect.fromLTRB(-15.0, -20.0, 15.0, 0.0)); }); @@ -335,7 +335,7 @@ void _testCullRectComputation() { builder.build(); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect(picture.optimalLocalCullRect, Rect.zero); expect(picture.debugExactGlobalCullRect, Rect.zero); }); @@ -378,7 +378,7 @@ void _testCullRectComputation() { await matchGoldenFile('compositing_cull_rect_rotated.png', region: region); - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; expect( picture.optimalLocalCullRect, within( @@ -510,7 +510,7 @@ void _testCullRectComputation() { await matchGoldenFile('compositing_3d_rotate1.png', region: region); // ignore: unused_local_variable - final PersistedStandardPicture picture = enumeratePictures().single; + final PersistedPicture picture = enumeratePictures().single; // TODO(https://github.com/flutter/flutter/issues/40395): // Needs ability to set iframe to 500,100 size. Current screen seems to be 500,500. // expect( diff --git a/lib/web_ui/test/golden_tests/engine/scuba.dart b/lib/web_ui/test/golden_tests/engine/scuba.dart index 821b9dddd0078802b0bab75d749eec6ee038a013..e735967a93bf5951d7ac6d668faf611390ac3358 100644 --- a/lib/web_ui/test/golden_tests/engine/scuba.dart +++ b/lib/web_ui/test/golden_tests/engine/scuba.dart @@ -91,7 +91,7 @@ typedef CanvasTest = FutureOr Function(EngineCanvas canvas); /// Runs the given test [body] with each type of canvas. void testEachCanvas(String description, CanvasTest body, - {double maxDiffRate, bool bSkipHoudini = false}) { + {double maxDiffRate}) { const ui.Rect bounds = ui.Rect.fromLTWH(0, 0, 600, 800); test('$description (bitmap)', () { try { @@ -123,18 +123,6 @@ void testEachCanvas(String description, CanvasTest body, TextMeasurementService.clearCache(); } }); - if (!bSkipHoudini) { - test('$description (houdini)', () { - try { - TextMeasurementService.initialize(rulerCacheCapacity: 2); - WebExperiments.instance.useCanvasText = false; - return body(HoudiniCanvas(bounds)); - } finally { - WebExperiments.instance.useCanvasText = null; - TextMeasurementService.clearCache(); - } - }); - } } final ui.TextStyle _defaultTextStyle = ui.TextStyle( diff --git a/lib/web_ui/test/golden_tests/engine/text_placeholders_test.dart b/lib/web_ui/test/golden_tests/engine/text_placeholders_test.dart index 9b9fd816a3ee38012248d6ef94bca4264deb2ab7..9c1d6a50dc3ef5fa67b789ef6bfe063ca08c036f 100644 --- a/lib/web_ui/test/golden_tests/engine/text_placeholders_test.dart +++ b/lib/web_ui/test/golden_tests/engine/text_placeholders_test.dart @@ -30,7 +30,7 @@ void main() async { recordingCanvas.endRecording(); recordingCanvas.apply(canvas, screenRect); return scuba.diffCanvasScreenshot(canvas, 'text_with_placeholders'); - }, bSkipHoudini: true); + }); } const Color black = Color(0xFF000000); diff --git a/lib/web_ui/test/golden_tests/engine/text_style_golden_test.dart b/lib/web_ui/test/golden_tests/engine/text_style_golden_test.dart index c8ec261e8b72a37e16a4f8d7bbe242b2bc364b5b..9727dd596c38fcb75eeedc27558f07df9b7bdaa9 100644 --- a/lib/web_ui/test/golden_tests/engine/text_style_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/text_style_golden_test.dart @@ -220,7 +220,7 @@ void main() async { testEachCanvas('draws text with a shadow', (EngineCanvas canvas) { drawTextWithShadow(canvas); return scuba.diffCanvasScreenshot(canvas, 'text_shadow', maxDiffRatePercent: 0.2); - }, bSkipHoudini: true); + }); testEachCanvas('Handles disabled strut style', (EngineCanvas canvas) { // Flutter uses [StrutStyle.disabled] for the [SelectableText] widget. This