diff --git a/DEPS b/DEPS index fa4e448eeabe4..5eb34d1e1dcdb 100644 --- a/DEPS +++ b/DEPS @@ -109,7 +109,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + '337fdd987f500ca48902aef9abbcde98be2803c7', + 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'af893d511e89f93194f86dae8a4ef39e3b3fe59b', # Fuchsia compatibility # diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 643fd39f80bf1..b8c5efd95cf05 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1005,6 +1005,7 @@ FILE: ../../../flutter/lib/ui/isolate_name_server/isolate_name_server_natives.cc FILE: ../../../flutter/lib/ui/isolate_name_server/isolate_name_server_natives.h FILE: ../../../flutter/lib/ui/key.dart FILE: ../../../flutter/lib/ui/lerp.dart +FILE: ../../../flutter/lib/ui/math.dart FILE: ../../../flutter/lib/ui/natives.dart FILE: ../../../flutter/lib/ui/painting.dart FILE: ../../../flutter/lib/ui/painting/canvas.cc @@ -1149,6 +1150,7 @@ FILE: ../../../flutter/lib/web_ui/lib/hash_codes.dart FILE: ../../../flutter/lib/web_ui/lib/initialization.dart FILE: ../../../flutter/lib/web_ui/lib/key.dart FILE: ../../../flutter/lib/web_ui/lib/lerp.dart +FILE: ../../../flutter/lib/web_ui/lib/math.dart FILE: ../../../flutter/lib/web_ui/lib/natives.dart FILE: ../../../flutter/lib/web_ui/lib/painting.dart FILE: ../../../flutter/lib/web_ui/lib/path.dart diff --git a/lib/ui/dart_ui.gni b/lib/ui/dart_ui.gni index 96a7b06fbd918..7059f7ab2ae08 100644 --- a/lib/ui/dart_ui.gni +++ b/lib/ui/dart_ui.gni @@ -12,6 +12,7 @@ dart_ui_files = [ "//flutter/lib/ui/isolate_name_server.dart", "//flutter/lib/ui/key.dart", "//flutter/lib/ui/lerp.dart", + "//flutter/lib/ui/math.dart", "//flutter/lib/ui/natives.dart", "//flutter/lib/ui/painting.dart", "//flutter/lib/ui/platform_dispatcher.dart", diff --git a/lib/ui/geometry.dart b/lib/ui/geometry.dart index 8a1fe6f00f9ca..7a395253e2356 100644 --- a/lib/ui/geometry.dart +++ b/lib/ui/geometry.dart @@ -939,6 +939,37 @@ class Radius { /// You can use [Radius.zero] with [RRect] to have right-angle corners. static const Radius zero = Radius.circular(0.0); + /// Returns this [Radius], with values clamped to the given min and max + /// [Radius] values. + /// + /// The `min` value defaults to `Radius.circular(-double.infinity)`, and + /// the `max` value defaults to `Radius.circular(double.infinity)`. + Radius clamp({Radius? minimum, Radius? maximum}) { + minimum ??= const Radius.circular(-double.infinity); + maximum ??= const Radius.circular(double.infinity); + return Radius.elliptical( + clampDouble(x, minimum.x, maximum.x), + clampDouble(y, minimum.y, maximum.y), + ); + } + + /// Returns this [Radius], with values clamped to the given min and max + /// values in each dimension + /// + /// The `minimumX` and `minimumY` values default to `-double.infinity`, and + /// the `maximumX` and `maximumY` values default to `double.infinity`. + Radius clampValues({ + double? minimumX, + double? minimumY, + double? maximumX, + double? maximumY, + }) { + return Radius.elliptical( + clampDouble(x, minimumX ?? -double.infinity, maximumX ?? double.infinity), + clampDouble(y, minimumY ?? -double.infinity, maximumY ?? double.infinity), + ); + } + /// Unary negation operator. /// /// Returns a Radius with the distances negated. @@ -1056,8 +1087,16 @@ class Radius { class RRect { /// Construct a rounded rectangle from its left, top, right, and bottom edges, /// and the same radii along its horizontal axis and its vertical axis. - const RRect.fromLTRBXY(double left, double top, double right, double bottom, - double radiusX, double radiusY) : this._raw( + /// + /// Will assert in debug mode if `radiusX` or `radiusY` are negative. + const RRect.fromLTRBXY( + double left, + double top, + double right, + double bottom, + double radiusX, + double radiusY, + ) : this._raw( top: top, left: left, right: right, @@ -1074,8 +1113,15 @@ class RRect { /// Construct a rounded rectangle from its left, top, right, and bottom edges, /// and the same radius in each corner. - RRect.fromLTRBR(double left, double top, double right, double bottom, - Radius radius) + /// + /// Will assert in debug mode if the `radius` is negative in either x or y. + RRect.fromLTRBR( + double left, + double top, + double right, + double bottom, + Radius radius, + ) : this._raw( top: top, left: left, @@ -1093,6 +1139,8 @@ class RRect { /// Construct a rounded rectangle from its bounding box and the same radii /// along its horizontal axis and its vertical axis. + /// + /// Will assert in debug mode if `radiusX` or `radiusY` are negative. RRect.fromRectXY(Rect rect, double radiusX, double radiusY) : this._raw( top: rect.top, @@ -1111,6 +1159,8 @@ class RRect { /// Construct a rounded rectangle from its bounding box and a radius that is /// the same in each corner. + /// + /// Will assert in debug mode if the `radius` is negative in either x or y. RRect.fromRectAndRadius(Rect rect, Radius radius) : this._raw( top: rect.top, @@ -1130,7 +1180,8 @@ class RRect { /// Construct a rounded rectangle from its left, top, right, and bottom edges, /// and topLeft, topRight, bottomRight, and bottomLeft radii. /// - /// The corner radii default to [Radius.zero], i.e. right-angled corners. + /// The corner radii default to [Radius.zero], i.e. right-angled corners. Will + /// assert in debug mode if any of the radii are negative in either x or y. RRect.fromLTRBAndCorners( double left, double top, @@ -1158,7 +1209,8 @@ class RRect { /// Construct a rounded rectangle from its bounding box and and topLeft, /// topRight, bottomRight, and bottomLeft radii. /// - /// The corner radii default to [Radius.zero], i.e. right-angled corners + /// The corner radii default to [Radius.zero], i.e. right-angled corners. Will + /// assert in debug mode if any of the radii are negative in either x or y. RRect.fromRectAndCorners( Rect rect, { @@ -1206,7 +1258,15 @@ class RRect { assert(brRadiusX != null), assert(brRadiusY != null), assert(blRadiusX != null), - assert(blRadiusY != null); + assert(blRadiusY != null), + assert(tlRadiusX >= 0), + assert(tlRadiusY >= 0), + assert(trRadiusX >= 0), + assert(trRadiusY >= 0), + assert(brRadiusX >= 0), + assert(brRadiusY >= 0), + assert(blRadiusX >= 0), + assert(blRadiusY >= 0); Float32List _getValue32() { final Float32List result = Float32List(12); @@ -1302,14 +1362,14 @@ class RRect { top: top - delta, right: right + delta, bottom: bottom + delta, - tlRadiusX: tlRadiusX + delta, - tlRadiusY: tlRadiusY + delta, - trRadiusX: trRadiusX + delta, - trRadiusY: trRadiusY + delta, - blRadiusX: blRadiusX + delta, - blRadiusY: blRadiusY + delta, - brRadiusX: brRadiusX + delta, - brRadiusY: brRadiusY + delta, + tlRadiusX: math.max(0, tlRadiusX + delta), + tlRadiusY: math.max(0, tlRadiusY + delta), + trRadiusX: math.max(0, trRadiusX + delta), + trRadiusY: math.max(0, trRadiusY + delta), + blRadiusX: math.max(0, blRadiusX + delta), + blRadiusY: math.max(0, blRadiusY + delta), + brRadiusX: math.max(0, brRadiusX + delta), + brRadiusY: math.max(0, brRadiusY + delta), ); } @@ -1472,6 +1532,7 @@ class RRect { scale = _getMin(scale, tlRadiusX, trRadiusX, width); scale = _getMin(scale, trRadiusY, brRadiusY, height); scale = _getMin(scale, brRadiusX, blRadiusX, width); + assert(scale >= 0); if (scale < 1.0) { return RRect._raw( @@ -1590,14 +1651,14 @@ class RRect { top: a.top * k, right: a.right * k, bottom: a.bottom * k, - tlRadiusX: a.tlRadiusX * k, - tlRadiusY: a.tlRadiusY * k, - trRadiusX: a.trRadiusX * k, - trRadiusY: a.trRadiusY * k, - brRadiusX: a.brRadiusX * k, - brRadiusY: a.brRadiusY * k, - blRadiusX: a.blRadiusX * k, - blRadiusY: a.blRadiusY * k, + tlRadiusX: math.max(0, a.tlRadiusX * k), + tlRadiusY: math.max(0, a.tlRadiusY * k), + trRadiusX: math.max(0, a.trRadiusX * k), + trRadiusY: math.max(0, a.trRadiusY * k), + brRadiusX: math.max(0, a.brRadiusX * k), + brRadiusY: math.max(0, a.brRadiusY * k), + blRadiusX: math.max(0, a.blRadiusX * k), + blRadiusY: math.max(0, a.blRadiusY * k), ); } } else { @@ -1607,14 +1668,14 @@ class RRect { top: b.top * t, right: b.right * t, bottom: b.bottom * t, - tlRadiusX: b.tlRadiusX * t, - tlRadiusY: b.tlRadiusY * t, - trRadiusX: b.trRadiusX * t, - trRadiusY: b.trRadiusY * t, - brRadiusX: b.brRadiusX * t, - brRadiusY: b.brRadiusY * t, - blRadiusX: b.blRadiusX * t, - blRadiusY: b.blRadiusY * t, + tlRadiusX: math.max(0, b.tlRadiusX * t), + tlRadiusY: math.max(0, b.tlRadiusY * t), + trRadiusX: math.max(0, b.trRadiusX * t), + trRadiusY: math.max(0, b.trRadiusY * t), + brRadiusX: math.max(0, b.brRadiusX * t), + brRadiusY: math.max(0, b.brRadiusY * t), + blRadiusX: math.max(0, b.blRadiusX * t), + blRadiusY: math.max(0, b.blRadiusY * t), ); } else { return RRect._raw( @@ -1622,14 +1683,14 @@ class RRect { top: _lerpDouble(a.top, b.top, t), right: _lerpDouble(a.right, b.right, t), bottom: _lerpDouble(a.bottom, b.bottom, t), - tlRadiusX: _lerpDouble(a.tlRadiusX, b.tlRadiusX, t), - tlRadiusY: _lerpDouble(a.tlRadiusY, b.tlRadiusY, t), - trRadiusX: _lerpDouble(a.trRadiusX, b.trRadiusX, t), - trRadiusY: _lerpDouble(a.trRadiusY, b.trRadiusY, t), - brRadiusX: _lerpDouble(a.brRadiusX, b.brRadiusX, t), - brRadiusY: _lerpDouble(a.brRadiusY, b.brRadiusY, t), - blRadiusX: _lerpDouble(a.blRadiusX, b.blRadiusX, t), - blRadiusY: _lerpDouble(a.blRadiusY, b.blRadiusY, t), + tlRadiusX: math.max(0, _lerpDouble(a.tlRadiusX, b.tlRadiusX, t)), + tlRadiusY: math.max(0, _lerpDouble(a.tlRadiusY, b.tlRadiusY, t)), + trRadiusX: math.max(0, _lerpDouble(a.trRadiusX, b.trRadiusX, t)), + trRadiusY: math.max(0, _lerpDouble(a.trRadiusY, b.trRadiusY, t)), + brRadiusX: math.max(0, _lerpDouble(a.brRadiusX, b.brRadiusX, t)), + brRadiusY: math.max(0, _lerpDouble(a.brRadiusY, b.brRadiusY, t)), + blRadiusX: math.max(0, _lerpDouble(a.blRadiusX, b.blRadiusX, t)), + blRadiusY: math.max(0, _lerpDouble(a.blRadiusY, b.blRadiusY, t)), ); } } diff --git a/lib/ui/math.dart b/lib/ui/math.dart new file mode 100644 index 0000000000000..aeb835a2fc287 --- /dev/null +++ b/lib/ui/math.dart @@ -0,0 +1,25 @@ +// 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. + +part of dart.ui; + +/// Same as [num.clamp] but optimized for a non-null [double]. +/// +/// This is faster because it avoids polymorphism, boxing, and special cases for +/// floating point numbers. +// +// See also: //dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart +double clampDouble(double x, double min, double max) { + assert(min <= max && !max.isNaN && !min.isNaN); + if (x < min) { + return min; + } + if (x > max) { + return max; + } + if (x.isNaN) { + return max; + } + return x; +} diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 3c0a7ad4ae07c..316de56944caf 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -320,7 +320,7 @@ class Color { /// The [opacity] value may not be null. static int getAlphaFromOpacity(double opacity) { assert(opacity != null); - return (opacity.clamp(0.0, 1.0) * 255).round(); + return (clampDouble(opacity, 0.0, 1.0) * 255).round(); } @override diff --git a/lib/ui/ui.dart b/lib/ui/ui.dart index 2695a80916ee4..36818d3c74480 100644 --- a/lib/ui/ui.dart +++ b/lib/ui/ui.dart @@ -31,6 +31,7 @@ part 'hooks.dart'; part 'isolate_name_server.dart'; part 'key.dart'; part 'lerp.dart'; +part 'math.dart'; part 'natives.dart'; part 'painting.dart'; part 'platform_dispatcher.dart'; diff --git a/lib/web_ui/dev/build.dart b/lib/web_ui/dev/build.dart index bf5b55d0a17e0..087e4fa00cda8 100644 --- a/lib/web_ui/dev/build.dart +++ b/lib/web_ui/dev/build.dart @@ -48,7 +48,7 @@ class BuildCommand extends Command with ArgUtils { if (buildCanvasKit) { steps.addAll([ GnPipelineStep(target: 'canvaskit'), - NinjaPipelineStep(target: environment.canvasKitOutDir), + NinjaPipelineStep(target: environment.wasmReleaseOutDir), ]); } final Pipeline buildPipeline = Pipeline(steps: steps); @@ -74,7 +74,7 @@ class BuildCommand extends Command with ArgUtils { /// state. GN is pretty quick though, so it's OK to not support interruption. class GnPipelineStep extends ProcessStep { GnPipelineStep({this.target = 'engine'}) - : assert(target == 'engine' || target == 'sdk'); + : assert(target == 'engine' || target == 'canvaskit'); @override String get description => 'gn'; @@ -89,7 +89,7 @@ class GnPipelineStep extends ProcessStep { @override Future createProcess() { - print('Running gn...'); + print('Running gn for $target...'); final List gnArgs = []; if (target == 'engine') { gnArgs.addAll([ @@ -98,7 +98,10 @@ class GnPipelineStep extends ProcessStep { '--full-dart-sdk', ]); } else if (target == 'canvaskit') { - gnArgs.add('--wasm'); + gnArgs.addAll([ + '--wasm', + '--runtime-mode=release', + ]); } else { throw StateError('Target was not engine or canvaskit: $target'); } diff --git a/lib/web_ui/dev/environment.dart b/lib/web_ui/dev/environment.dart index 1c5530ba2a0d0..35df6c0d0f164 100644 --- a/lib/web_ui/dev/environment.dart +++ b/lib/web_ui/dev/environment.dart @@ -25,8 +25,8 @@ class Environment { io.Directory(pathlib.join(engineSrcDir.path, 'out')); final io.Directory hostDebugUnoptDir = io.Directory(pathlib.join(outDir.path, 'host_debug_unopt')); - final io.Directory canvasKitOutDir = - io.Directory(pathlib.join(outDir.path, 'wasm_debug')); + final io.Directory wasmReleaseOutDir = + io.Directory(pathlib.join(outDir.path, 'wasm_release')); final io.Directory dartSdkDir = io.Directory(pathlib.join(hostDebugUnoptDir.path, 'dart-sdk')); final io.Directory webUiRootDir = io.Directory( @@ -44,6 +44,7 @@ class Environment { } } + return Environment._( self: self, webUiRootDir: webUiRootDir, @@ -51,7 +52,7 @@ class Environment { engineToolsDir: engineToolsDir, outDir: outDir, hostDebugUnoptDir: hostDebugUnoptDir, - canvasKitOutDir: canvasKitOutDir, + wasmReleaseOutDir: wasmReleaseOutDir, dartSdkDir: dartSdkDir, ); } @@ -63,7 +64,7 @@ class Environment { required this.engineToolsDir, required this.outDir, required this.hostDebugUnoptDir, - required this.canvasKitOutDir, + required this.wasmReleaseOutDir, required this.dartSdkDir, }); @@ -84,11 +85,13 @@ class Environment { /// This is where you'll find the ninja output, such as the Dart SDK. final io.Directory outDir; - /// The "host_debug_unopt" build of the Dart SDK. + /// The output directory for the host_debug_unopt build. final io.Directory hostDebugUnoptDir; - /// The output directory for the build of CanvasKit. - final io.Directory canvasKitOutDir; + /// The output directory for the wasm_release build. + /// + /// We build CanvasKit in release mode to reduce code size. + final io.Directory wasmReleaseOutDir; /// The root of the Dart SDK. final io.Directory dartSdkDir; diff --git a/lib/web_ui/dev/steps/compile_tests_step.dart b/lib/web_ui/dev/steps/compile_tests_step.dart index e9931b7b5ed00..2cc2d22fea99e 100644 --- a/lib/web_ui/dev/steps/compile_tests_step.dart +++ b/lib/web_ui/dev/steps/compile_tests_step.dart @@ -23,10 +23,12 @@ import '../utils.dart'; /// * test/ - compiled test code /// * test_images/ - test images copied from Skis sources. class CompileTestsStep implements PipelineStep { - CompileTestsStep({this.testFiles}); + CompileTestsStep({this.testFiles, this.useLocalCanvasKit = false}); final List? testFiles; + final bool useLocalCanvasKit; + @override String get description => 'compile_tests'; @@ -41,7 +43,7 @@ class CompileTestsStep implements PipelineStep { @override Future run() async { await environment.webUiBuildDir.create(); - await copyCanvasKitFiles(); + await copyCanvasKitFiles(useLocalCanvasKit: useLocalCanvasKit); await buildHostPage(); await copyTestFonts(); await copySkiaTestImages(); @@ -122,11 +124,13 @@ Future copySkiaTestImages() async { } } -Future copyCanvasKitFiles() async { +Future copyCanvasKitFiles({bool useLocalCanvasKit = false}) async { // If CanvasKit has been built locally, use that instead of the CIPD version. - final io.File localCanvasKitWasm = - io.File(pathlib.join(environment.canvasKitOutDir.path, 'canvaskit.wasm')); - final bool builtLocalCanvasKit = localCanvasKitWasm.existsSync(); + final io.File localCanvasKitWasm = io.File(pathlib.join( + environment.wasmReleaseOutDir.path, + 'canvaskit.wasm', + )); + final bool builtLocalCanvasKit = localCanvasKitWasm.existsSync() && useLocalCanvasKit; final io.Directory targetDir = io.Directory(pathlib.join( environment.webUiBuildDir.path, @@ -136,7 +140,10 @@ Future copyCanvasKitFiles() async { if (builtLocalCanvasKit) { final List canvasKitFiles = [ localCanvasKitWasm, - io.File(pathlib.join(environment.canvasKitOutDir.path, 'canvaskit.js')), + io.File(pathlib.join( + environment.wasmReleaseOutDir.path, + 'canvaskit.js', + )), ]; for (final io.File file in canvasKitFiles) { final io.File normalTargetFile = io.File(pathlib.join( diff --git a/lib/web_ui/dev/test_runner.dart b/lib/web_ui/dev/test_runner.dart index cb4628fe2d9a0..8984d2f7b25b9 100644 --- a/lib/web_ui/dev/test_runner.dart +++ b/lib/web_ui/dev/test_runner.dart @@ -73,6 +73,11 @@ class TestCommand extends Command with ArgUtils { help: 'Optional. The path to a local build of CanvasKit to use in ' 'tests. If omitted, the test runner uses the default CanvasKit ' 'build.', + ) + ..addFlag( + 'use-local-canvaskit', + help: 'Optional. Whether or not to use the locally built version of ' + 'CanvasKit in the tests.', ); } @@ -115,6 +120,9 @@ class TestCommand extends Command with ArgUtils { /// Path to a CanvasKit build. Overrides the default CanvasKit. String? get overridePathToCanvasKit => argResults!['canvaskit-path'] as String?; + /// Whether or not to use the locally built version of CanvasKit. + bool get useLocalCanvasKit => boolArg('use-local-canvaskit'); + @override Future run() async { final List testFiles = runAllTests @@ -123,7 +131,7 @@ class TestCommand extends Command with ArgUtils { final Pipeline testPipeline = Pipeline(steps: [ if (isWatchMode) ClearTerminalScreenStep(), - CompileTestsStep(testFiles: testFiles), + CompileTestsStep(testFiles: testFiles, useLocalCanvasKit: useLocalCanvasKit), RunTestsStep( browserName: browserName, testFiles: testFiles, diff --git a/lib/web_ui/lib/geometry.dart b/lib/web_ui/lib/geometry.dart index 3d60b13e92601..7d17c73f14131 100644 --- a/lib/web_ui/lib/geometry.dart +++ b/lib/web_ui/lib/geometry.dart @@ -341,6 +341,25 @@ class Radius { final double x; final double y; static const Radius zero = Radius.circular(0.0); + Radius clamp({Radius? minimum, Radius? maximum}) { + minimum ??= const Radius.circular(-double.infinity); + maximum ??= const Radius.circular(double.infinity); + return Radius.elliptical( + clampDouble(x, minimum.x, maximum.x), + clampDouble(y, minimum.y, maximum.y), + ); + } + Radius clampValues({ + double? minimumX, + double? minimumY, + double? maximumX, + double? maximumY, + }) { + return Radius.elliptical( + clampDouble(x, minimumX ?? -double.infinity, maximumX ?? double.infinity), + clampDouble(y, minimumY ?? -double.infinity, maximumY ?? double.infinity), + ); + } Radius operator -() => Radius.elliptical(-x, -y); Radius operator -(Radius other) => Radius.elliptical(x - other.x, y - other.y); Radius operator +(Radius other) => Radius.elliptical(x + other.x, y + other.y); @@ -559,6 +578,14 @@ class RRect { assert(brRadiusY != null), assert(blRadiusX != null), assert(blRadiusY != null), + assert(tlRadiusX >= 0), + assert(tlRadiusY >= 0), + assert(trRadiusX >= 0), + assert(trRadiusY >= 0), + assert(brRadiusX >= 0), + assert(brRadiusY >= 0), + assert(blRadiusX >= 0), + assert(blRadiusY >= 0), webOnlyUniformRadii = uniformRadii; final double left; @@ -604,14 +631,14 @@ class RRect { top: top - delta, right: right + delta, bottom: bottom + delta, - tlRadiusX: tlRadiusX + delta, - tlRadiusY: tlRadiusY + delta, - trRadiusX: trRadiusX + delta, - trRadiusY: trRadiusY + delta, - blRadiusX: blRadiusX + delta, - blRadiusY: blRadiusY + delta, - brRadiusX: brRadiusX + delta, - brRadiusY: brRadiusY + delta, + tlRadiusX: math.max(0, tlRadiusX + delta), + tlRadiusY: math.max(0, tlRadiusY + delta), + trRadiusX: math.max(0, trRadiusX + delta), + trRadiusY: math.max(0, trRadiusY + delta), + blRadiusX: math.max(0, blRadiusX + delta), + blRadiusY: math.max(0, blRadiusY + delta), + brRadiusX: math.max(0, brRadiusX + delta), + brRadiusY: math.max(0, brRadiusY + delta), ); } @@ -816,14 +843,14 @@ class RRect { top: a.top * k, right: a.right * k, bottom: a.bottom * k, - tlRadiusX: a.tlRadiusX * k, - tlRadiusY: a.tlRadiusY * k, - trRadiusX: a.trRadiusX * k, - trRadiusY: a.trRadiusY * k, - brRadiusX: a.brRadiusX * k, - brRadiusY: a.brRadiusY * k, - blRadiusX: a.blRadiusX * k, - blRadiusY: a.blRadiusY * k, + tlRadiusX: math.max(0, a.tlRadiusX * k), + tlRadiusY: math.max(0, a.tlRadiusY * k), + trRadiusX: math.max(0, a.trRadiusX * k), + trRadiusY: math.max(0, a.trRadiusY * k), + brRadiusX: math.max(0, a.brRadiusX * k), + brRadiusY: math.max(0, a.brRadiusY * k), + blRadiusX: math.max(0, a.blRadiusX * k), + blRadiusY: math.max(0, a.blRadiusY * k), ); } } else { @@ -833,14 +860,14 @@ class RRect { top: b.top * t, right: b.right * t, bottom: b.bottom * t, - tlRadiusX: b.tlRadiusX * t, - tlRadiusY: b.tlRadiusY * t, - trRadiusX: b.trRadiusX * t, - trRadiusY: b.trRadiusY * t, - brRadiusX: b.brRadiusX * t, - brRadiusY: b.brRadiusY * t, - blRadiusX: b.blRadiusX * t, - blRadiusY: b.blRadiusY * t, + tlRadiusX: math.max(0, b.tlRadiusX * t), + tlRadiusY: math.max(0, b.tlRadiusY * t), + trRadiusX: math.max(0, b.trRadiusX * t), + trRadiusY: math.max(0, b.trRadiusY * t), + brRadiusX: math.max(0, b.brRadiusX * t), + brRadiusY: math.max(0, b.brRadiusY * t), + blRadiusX: math.max(0, b.blRadiusX * t), + blRadiusY: math.max(0, b.blRadiusY * t), ); } else { return RRect._raw( @@ -848,14 +875,14 @@ class RRect { top: _lerpDouble(a.top, b.top, t), right: _lerpDouble(a.right, b.right, t), bottom: _lerpDouble(a.bottom, b.bottom, t), - tlRadiusX: _lerpDouble(a.tlRadiusX, b.tlRadiusX, t), - tlRadiusY: _lerpDouble(a.tlRadiusY, b.tlRadiusY, t), - trRadiusX: _lerpDouble(a.trRadiusX, b.trRadiusX, t), - trRadiusY: _lerpDouble(a.trRadiusY, b.trRadiusY, t), - brRadiusX: _lerpDouble(a.brRadiusX, b.brRadiusX, t), - brRadiusY: _lerpDouble(a.brRadiusY, b.brRadiusY, t), - blRadiusX: _lerpDouble(a.blRadiusX, b.blRadiusX, t), - blRadiusY: _lerpDouble(a.blRadiusY, b.blRadiusY, t), + tlRadiusX: math.max(0, _lerpDouble(a.tlRadiusX, b.tlRadiusX, t)), + tlRadiusY: math.max(0, _lerpDouble(a.tlRadiusY, b.tlRadiusY, t)), + trRadiusX: math.max(0, _lerpDouble(a.trRadiusX, b.trRadiusX, t)), + trRadiusY: math.max(0, _lerpDouble(a.trRadiusY, b.trRadiusY, t)), + brRadiusX: math.max(0, _lerpDouble(a.brRadiusX, b.brRadiusX, t)), + brRadiusY: math.max(0, _lerpDouble(a.brRadiusY, b.brRadiusY, t)), + blRadiusX: math.max(0, _lerpDouble(a.blRadiusX, b.blRadiusX, t)), + blRadiusY: math.max(0, _lerpDouble(a.blRadiusY, b.blRadiusY, t)), ); } } diff --git a/lib/web_ui/lib/math.dart b/lib/web_ui/lib/math.dart new file mode 100644 index 0000000000000..adea127ccec1f --- /dev/null +++ b/lib/web_ui/lib/math.dart @@ -0,0 +1,19 @@ +// 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. + +part of ui; + +double clampDouble(double x, double min, double max) { + assert(min <= max && !max.isNaN && !min.isNaN); + if (x < min) { + return min; + } + if (x > max) { + return max; + } + if (x.isNaN) { + return max; + } + return x; +} diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index 3ab2ba0d3e69b..03e44ecce0b0d 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -146,7 +146,7 @@ class Color { static int getAlphaFromOpacity(double opacity) { assert(opacity != null); - return (opacity.clamp(0.0, 1.0) * 255).round(); + return (clampDouble(opacity, 0.0, 1.0) * 255).round(); } @override diff --git a/lib/web_ui/lib/src/engine/html/recording_canvas.dart b/lib/web_ui/lib/src/engine/html/recording_canvas.dart index b1f86da42f8fe..a7638d1dfcbcf 100644 --- a/lib/web_ui/lib/src/engine/html/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/recording_canvas.dart @@ -23,16 +23,8 @@ import 'shaders/image_shader.dart'; /// Enable this to print every command applied by a canvas. const bool _debugDumpPaintCommands = false; -// Returns the squared length of the x, y (of a border radius) -// It normalizes x, y values before working with them, by -// assuming anything < 0 to be 0, because flutter may pass -// negative radii (which Skia assumes to be 0), see: -// https://skia.org/user/api/SkRRect_Reference#SkRRect_inset -double _measureBorderRadius(double x, double y) { - final double clampedX = x < 0 ? 0 : x; - final double clampedY = y < 0 ? 0 : y; - return clampedX * clampedX + clampedY * clampedY; -} +// Returns the squared length of the x, y (of a border radius). +double _measureBorderRadius(double x, double y) => x*x + y*y; /// Records canvas commands to be applied to a [EngineCanvas]. /// diff --git a/lib/web_ui/lib/ui.dart b/lib/web_ui/lib/ui.dart index a01d53936b6e0..1878d4faa7b39 100644 --- a/lib/web_ui/lib/ui.dart +++ b/lib/web_ui/lib/ui.dart @@ -24,6 +24,7 @@ part 'hash_codes.dart'; part 'initialization.dart'; part 'key.dart'; part 'lerp.dart'; +part 'math.dart'; part 'natives.dart'; part 'painting.dart'; part 'path.dart'; diff --git a/lib/web_ui/test/engine/recording_canvas_test.dart b/lib/web_ui/test/engine/recording_canvas_test.dart index b3f80d1bb287e..1e4666ee14858 100644 --- a/lib/web_ui/test/engine/recording_canvas_test.dart +++ b/lib/web_ui/test/engine/recording_canvas_test.dart @@ -121,7 +121,7 @@ void testMain() { expect(mockCanvas.methodCallLog.single.methodName, 'endOfPaint'); }); - test('negative corners in inner RRect get passed through to draw', () { + test('deflated corners in inner RRect get passed through to draw', () { // This comes from github issue #40728 final RRect outer = RRect.fromRectAndCorners( const Rect.fromLTWH(0, 0, 88, 48), @@ -129,9 +129,8 @@ void testMain() { bottomLeft: const Radius.circular(6)); final RRect inner = outer.deflate(1); - // If these assertions fail, check [_measureBorderRadius] in recording_canvas.dart - expect(inner.brRadius, equals(const Radius.circular(-1))); - expect(inner.trRadius, equals(const Radius.circular(-1))); + expect(inner.brRadius, equals(Radius.zero)); + expect(inner.trRadius, equals(Radius.zero)); underTest.drawDRRect(outer, inner, somePaint); underTest.endRecording(); diff --git a/lib/web_ui/test/geometry_test.dart b/lib/web_ui/test/geometry_test.dart index 62b7f528cb7af..24150d801cdf4 100644 --- a/lib/web_ui/test/geometry_test.dart +++ b/lib/web_ui/test/geometry_test.dart @@ -75,4 +75,220 @@ void testMain() { expect(const Size(-1.0, -1.0).aspectRatio, 1.0); expect(const Size(3.0, 4.0).aspectRatio, 3.0 / 4.0); }); + test('Radius.clamp() operates as expected', () { + final RRect rrectMin = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.circular(-100).clamp(minimum: Radius.zero)); + + expect(rrectMin.left, 1); + expect(rrectMin.top, 3); + expect(rrectMin.right, 5); + expect(rrectMin.bottom, 7); + expect(rrectMin.trRadius, equals(const Radius.circular(0))); + expect(rrectMin.blRadius, equals(const Radius.circular(0))); + + final RRect rrectMax = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.circular(100).clamp(maximum: const Radius.circular(10))); + + expect(rrectMax.left, 1); + expect(rrectMax.top, 3); + expect(rrectMax.right, 5); + expect(rrectMax.bottom, 7); + expect(rrectMax.trRadius, equals(const Radius.circular(10))); + expect(rrectMax.blRadius, equals(const Radius.circular(10))); + + final RRect rrectMix = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.elliptical(-100, 100).clamp(minimum: Radius.zero, maximum: const Radius.circular(10))); + + expect(rrectMix.left, 1); + expect(rrectMix.top, 3); + expect(rrectMix.right, 5); + expect(rrectMix.bottom, 7); + expect(rrectMix.trRadius, equals(const Radius.elliptical(0, 10))); + expect(rrectMix.blRadius, equals(const Radius.elliptical(0, 10))); + + final RRect rrectMix1 = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.elliptical(100, -100).clamp(minimum: Radius.zero, maximum: const Radius.circular(10))); + + expect(rrectMix1.left, 1); + expect(rrectMix1.top, 3); + expect(rrectMix1.right, 5); + expect(rrectMix1.bottom, 7); + expect(rrectMix1.trRadius, equals(const Radius.elliptical(10, 0))); + expect(rrectMix1.blRadius, equals(const Radius.elliptical(10, 0))); + }); + test('Radius.clampValues() operates as expected', () { + final RRect rrectMin = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.circular(-100).clampValues(minimumX: 0, minimumY: 0)); + + expect(rrectMin.left, 1); + expect(rrectMin.top, 3); + expect(rrectMin.right, 5); + expect(rrectMin.bottom, 7); + expect(rrectMin.trRadius, equals(const Radius.circular(0))); + expect(rrectMin.blRadius, equals(const Radius.circular(0))); + + final RRect rrectMax = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.circular(100).clampValues(maximumX: 10, maximumY: 20)); + + expect(rrectMax.left, 1); + expect(rrectMax.top, 3); + expect(rrectMax.right, 5); + expect(rrectMax.bottom, 7); + expect(rrectMax.trRadius, equals(const Radius.elliptical(10, 20))); + expect(rrectMax.blRadius, equals(const Radius.elliptical(10, 20))); + + final RRect rrectMix = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.elliptical(-100, 100).clampValues(minimumX: 5, minimumY: 6, maximumX: 10, maximumY: 20)); + + expect(rrectMix.left, 1); + expect(rrectMix.top, 3); + expect(rrectMix.right, 5); + expect(rrectMix.bottom, 7); + expect(rrectMix.trRadius, equals(const Radius.elliptical(5, 20))); + expect(rrectMix.blRadius, equals(const Radius.elliptical(5, 20))); + + final RRect rrectMix2 = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.elliptical(100, -100).clampValues(minimumX: 5, minimumY: 6, maximumX: 10, maximumY: 20)); + + expect(rrectMix2.left, 1); + expect(rrectMix2.top, 3); + expect(rrectMix2.right, 5); + expect(rrectMix2.bottom, 7); + expect(rrectMix2.trRadius, equals(const Radius.elliptical(10, 6))); + expect(rrectMix2.blRadius, equals(const Radius.elliptical(10, 6))); + }); + test('RRect asserts when corner radii are negative', () { + bool assertsEnabled = false; + assert(() { + assertsEnabled = true; + return true; + }()); + if (!assertsEnabled) { + return; + } + + expect(() { + RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + topLeft: const Radius.circular(-1), + ); + }, throwsA(isA())); + + expect(() { + RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + topRight: const Radius.circular(-2), + ); + }, throwsA(isA())); + + expect(() { + RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + bottomLeft: const Radius.circular(-3), + ); + }, throwsA(isA())); + + expect(() { + RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + bottomRight: const Radius.circular(-4), + ); + }, throwsA(isA())); + }); + test('RRect.inflate clamps when deflating past zero', () { + RRect rrect = RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + topLeft: const Radius.circular(1), + topRight: const Radius.circular(2), + bottomLeft: const Radius.circular(3), + bottomRight: const Radius.circular(4), + ).inflate(-1); + + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 1); + expect(rrect.trRadiusY, 1); + expect(rrect.blRadiusX, 2); + expect(rrect.blRadiusY, 2); + expect(rrect.brRadiusX, 3); + expect(rrect.brRadiusY, 3); + + rrect = rrect.inflate(-1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 1); + expect(rrect.blRadiusY, 1); + expect(rrect.brRadiusX, 2); + expect(rrect.brRadiusY, 2); + + rrect = rrect.inflate(-1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 0); + expect(rrect.blRadiusY, 0); + expect(rrect.brRadiusX, 1); + expect(rrect.brRadiusY, 1); + + rrect = rrect.inflate(-1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 0); + expect(rrect.blRadiusY, 0); + expect(rrect.brRadiusX, 0); + expect(rrect.brRadiusY, 0); + }); + test('RRect.deflate clamps when deflating past zero', () { + RRect rrect = RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + topLeft: const Radius.circular(1), + topRight: const Radius.circular(2), + bottomLeft: const Radius.circular(3), + bottomRight: const Radius.circular(4), + ).deflate(1); + + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 1); + expect(rrect.trRadiusY, 1); + expect(rrect.blRadiusX, 2); + expect(rrect.blRadiusY, 2); + expect(rrect.brRadiusX, 3); + expect(rrect.brRadiusY, 3); + + rrect = rrect.deflate(1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 1); + expect(rrect.blRadiusY, 1); + expect(rrect.brRadiusX, 2); + expect(rrect.brRadiusY, 2); + + rrect = rrect.deflate(1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 0); + expect(rrect.blRadiusY, 0); + expect(rrect.brRadiusX, 1); + expect(rrect.brRadiusY, 1); + + rrect = rrect.deflate(1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 0); + expect(rrect.blRadiusY, 0); + expect(rrect.brRadiusX, 0); + expect(rrect.brRadiusY, 0); + }); } diff --git a/testing/dart/geometry_test.dart b/testing/dart/geometry_test.dart index 8e0101dd59b9c..8dc7f371eada6 100644 --- a/testing/dart/geometry_test.dart +++ b/testing/dart/geometry_test.dart @@ -292,4 +292,225 @@ void main() { expect(rrect.brRadiusX, 0.25); expect(rrect.brRadiusY, 0.75); }); + + test('Radius.clamp() operates as expected', () { + final RRect rrectMin = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.circular(-100).clamp(minimum: Radius.zero)); + + expect(rrectMin.left, 1); + expect(rrectMin.top, 3); + expect(rrectMin.right, 5); + expect(rrectMin.bottom, 7); + expect(rrectMin.trRadius, equals(const Radius.circular(0))); + expect(rrectMin.blRadius, equals(const Radius.circular(0))); + + final RRect rrectMax = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.circular(100).clamp(maximum: const Radius.circular(10))); + + expect(rrectMax.left, 1); + expect(rrectMax.top, 3); + expect(rrectMax.right, 5); + expect(rrectMax.bottom, 7); + expect(rrectMax.trRadius, equals(const Radius.circular(10))); + expect(rrectMax.blRadius, equals(const Radius.circular(10))); + + final RRect rrectMix = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.elliptical(-100, 100).clamp(minimum: Radius.zero, maximum: const Radius.circular(10))); + + expect(rrectMix.left, 1); + expect(rrectMix.top, 3); + expect(rrectMix.right, 5); + expect(rrectMix.bottom, 7); + expect(rrectMix.trRadius, equals(const Radius.elliptical(0, 10))); + expect(rrectMix.blRadius, equals(const Radius.elliptical(0, 10))); + + final RRect rrectMix1 = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.elliptical(100, -100).clamp(minimum: Radius.zero, maximum: const Radius.circular(10))); + + expect(rrectMix1.left, 1); + expect(rrectMix1.top, 3); + expect(rrectMix1.right, 5); + expect(rrectMix1.bottom, 7); + expect(rrectMix1.trRadius, equals(const Radius.elliptical(10, 0))); + expect(rrectMix1.blRadius, equals(const Radius.elliptical(10, 0))); + }); + + test('Radius.clampValues() operates as expected', () { + final RRect rrectMin = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.circular(-100).clampValues(minimumX: 0, minimumY: 0)); + + expect(rrectMin.left, 1); + expect(rrectMin.top, 3); + expect(rrectMin.right, 5); + expect(rrectMin.bottom, 7); + expect(rrectMin.trRadius, equals(const Radius.circular(0))); + expect(rrectMin.blRadius, equals(const Radius.circular(0))); + + final RRect rrectMax = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.circular(100).clampValues(maximumX: 10, maximumY: 20)); + + expect(rrectMax.left, 1); + expect(rrectMax.top, 3); + expect(rrectMax.right, 5); + expect(rrectMax.bottom, 7); + expect(rrectMax.trRadius, equals(const Radius.elliptical(10, 20))); + expect(rrectMax.blRadius, equals(const Radius.elliptical(10, 20))); + + final RRect rrectMix = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.elliptical(-100, 100).clampValues(minimumX: 5, minimumY: 6, maximumX: 10, maximumY: 20)); + + expect(rrectMix.left, 1); + expect(rrectMix.top, 3); + expect(rrectMix.right, 5); + expect(rrectMix.bottom, 7); + expect(rrectMix.trRadius, equals(const Radius.elliptical(5, 20))); + expect(rrectMix.blRadius, equals(const Radius.elliptical(5, 20))); + + final RRect rrectMix2 = RRect.fromLTRBR(1, 3, 5, 7, + const Radius.elliptical(100, -100).clampValues(minimumX: 5, minimumY: 6, maximumX: 10, maximumY: 20)); + + expect(rrectMix2.left, 1); + expect(rrectMix2.top, 3); + expect(rrectMix2.right, 5); + expect(rrectMix2.bottom, 7); + expect(rrectMix2.trRadius, equals(const Radius.elliptical(10, 6))); + expect(rrectMix2.blRadius, equals(const Radius.elliptical(10, 6))); + }); + + test('RRect asserts when corner radii are negative', () { + bool assertsEnabled = false; + assert(() { + assertsEnabled = true; + return true; + }()); + if (!assertsEnabled) { + return; + } + + expect(() { + RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + topLeft: const Radius.circular(-1), + ); + }, throwsA(isInstanceOf())); + + expect(() { + RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + topRight: const Radius.circular(-2), + ); + }, throwsA(isInstanceOf())); + + expect(() { + RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + bottomLeft: const Radius.circular(-3), + ); + }, throwsA(isInstanceOf())); + + expect(() { + RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + bottomRight: const Radius.circular(-4), + ); + }, throwsA(isInstanceOf())); + }); + + test('RRect.inflate clamps when deflating past zero', () { + RRect rrect = RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + topLeft: const Radius.circular(1), + topRight: const Radius.circular(2), + bottomLeft: const Radius.circular(3), + bottomRight: const Radius.circular(4), + ).inflate(-1); + + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 1); + expect(rrect.trRadiusY, 1); + expect(rrect.blRadiusX, 2); + expect(rrect.blRadiusY, 2); + expect(rrect.brRadiusX, 3); + expect(rrect.brRadiusY, 3); + + rrect = rrect.inflate(-1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 1); + expect(rrect.blRadiusY, 1); + expect(rrect.brRadiusX, 2); + expect(rrect.brRadiusY, 2); + + rrect = rrect.inflate(-1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 0); + expect(rrect.blRadiusY, 0); + expect(rrect.brRadiusX, 1); + expect(rrect.brRadiusY, 1); + + rrect = rrect.inflate(-1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 0); + expect(rrect.blRadiusY, 0); + expect(rrect.brRadiusX, 0); + expect(rrect.brRadiusY, 0); + }); + + test('RRect.deflate clamps when deflating past zero', () { + RRect rrect = RRect.fromRectAndCorners( + const Rect.fromLTRB(10.0, 20.0, 30.0, 40.0), + topLeft: const Radius.circular(1), + topRight: const Radius.circular(2), + bottomLeft: const Radius.circular(3), + bottomRight: const Radius.circular(4), + ).deflate(1); + + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 1); + expect(rrect.trRadiusY, 1); + expect(rrect.blRadiusX, 2); + expect(rrect.blRadiusY, 2); + expect(rrect.brRadiusX, 3); + expect(rrect.brRadiusY, 3); + + rrect = rrect.deflate(1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 1); + expect(rrect.blRadiusY, 1); + expect(rrect.brRadiusX, 2); + expect(rrect.brRadiusY, 2); + + rrect = rrect.deflate(1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 0); + expect(rrect.blRadiusY, 0); + expect(rrect.brRadiusX, 1); + expect(rrect.brRadiusY, 1); + + rrect = rrect.deflate(1); + expect(rrect.tlRadiusX, 0); + expect(rrect.tlRadiusY, 0); + expect(rrect.trRadiusX, 0); + expect(rrect.trRadiusY, 0); + expect(rrect.blRadiusX, 0); + expect(rrect.blRadiusY, 0); + expect(rrect.brRadiusX, 0); + expect(rrect.brRadiusY, 0); + }); } diff --git a/tools/gn b/tools/gn index 7b218ff116802..f49a3c26d0f8f 100755 --- a/tools/gn +++ b/tools/gn @@ -211,6 +211,14 @@ def to_gn_args(args): gn_args = {} + gn_args['is_debug'] = args.unoptimized + + # If building for WASM, set the GN args using 'to_gn_wasm_args' as most + # of the Flutter SDK specific arguments are unused. + if args.target_os == 'wasm': + to_gn_wasm_args(args, gn_args) + return gn_args + if args.bitcode: if args.target_os != 'ios': raise Exception('Bitcode is only supported for iOS') @@ -237,13 +245,8 @@ def to_gn_args(args): if args.enable_skshaper: gn_args['skia_use_icu'] = True - if args.target_os != 'wasm': - gn_args['flutter_always_use_skshaper'] = args.always_use_skshaper - else: - gn_args['skia_use_harfbuzz'] = True - gn_args['icu_use_data_file'] = False + gn_args['flutter_always_use_skshaper'] = args.always_use_skshaper gn_args['is_official_build'] = True # Disable Skia test utilities. - gn_args['is_debug'] = args.unoptimized gn_args['android_full_debug' ] = args.target_os == 'android' and args.unoptimized if args.clang is None: @@ -253,8 +256,6 @@ def to_gn_args(args): if args.target_os == 'android' or args.target_os == 'ios': gn_args['skia_gl_standard'] = 'gles' - elif args.target_os == 'wasm': - gn_args['skia_gl_standard'] = 'webgl' else: # We explicitly don't want to pick GL because we run GLES tests using SwiftShader. gn_args['skia_gl_standard'] = '' @@ -318,36 +319,34 @@ def to_gn_args(args): gn_args['dart_debug'] = True gn_args['dart_debug_optimization_level'] = '0' - # Flutter-specific arguments which don't apply for a CanvasKit build. - if args.target_os != 'wasm': - gn_args['flutter_use_fontconfig'] = args.enable_fontconfig - gn_args['flutter_enable_skshaper'] = args.enable_skshaper - gn_args['dart_component_kind' - ] = 'static_library' # Always link Dart in statically. - gn_args['embedder_for_target'] = args.embedder_for_target - gn_args['dart_lib_export_symbols'] = False - gn_args['flutter_runtime_mode'] = runtime_mode - gn_args['full_dart_sdk'] = args.full_dart_sdk - gn_args['dart_version_git_info'] = not args.no_dart_version_git_info - - gn_args['dart_lib_export_symbols'] = False - if runtime_mode == 'debug': - gn_args['dart_runtime_mode'] = 'develop' - elif runtime_mode == 'jit_release': - gn_args['dart_runtime_mode'] = 'release' - else: - gn_args['dart_runtime_mode'] = runtime_mode + gn_args['flutter_use_fontconfig'] = args.enable_fontconfig + gn_args['flutter_enable_skshaper'] = args.enable_skshaper + gn_args['dart_component_kind' + ] = 'static_library' # Always link Dart in statically. + gn_args['embedder_for_target'] = args.embedder_for_target + gn_args['dart_lib_export_symbols'] = False + gn_args['flutter_runtime_mode'] = runtime_mode + gn_args['full_dart_sdk'] = args.full_dart_sdk + gn_args['dart_version_git_info'] = not args.no_dart_version_git_info + + gn_args['dart_lib_export_symbols'] = False + if runtime_mode == 'debug': + gn_args['dart_runtime_mode'] = 'develop' + elif runtime_mode == 'jit_release': + gn_args['dart_runtime_mode'] = 'release' + else: + gn_args['dart_runtime_mode'] = runtime_mode - # Desktop embeddings can have more dependencies than the engine library, - # which can be problematic in some build environments (e.g., building on - # Linux will bring in pkg-config dependencies at generation time). These - # flags allow preventing those those targets from being part of the build - # tree. - gn_args['enable_desktop_embeddings'] = not args.disable_desktop_embeddings + # Desktop embeddings can have more dependencies than the engine library, + # which can be problematic in some build environments (e.g., building on + # Linux will bring in pkg-config dependencies at generation time). These + # flags allow preventing those those targets from being part of the build + # tree. + gn_args['enable_desktop_embeddings'] = not args.disable_desktop_embeddings - # Overrides whether Boring SSL is compiled with system as. Only meaningful - # on Android. - gn_args['bssl_use_clang_integrated_as'] = True + # Overrides whether Boring SSL is compiled with system as. Only meaningful + # on Android. + gn_args['bssl_use_clang_integrated_as'] = True if args.allow_deprecated_api_calls: gn_args['allow_deprecated_api_calls'] = args.allow_deprecated_api_calls @@ -417,8 +416,7 @@ def to_gn_args(args): else: gn_args['skia_use_gl'] = args.target_os != 'fuchsia' - if sys.platform == 'darwin' and args.target_os not in ['android', 'fuchsia', - 'wasm']: + if sys.platform == 'darwin' and args.target_os not in ['android', 'fuchsia']: # OpenGL is deprecated on macOS > 10.11. # This is not necessarily needed but enabling this until we have a way to # build a macOS metal only shell and a gl only shell. @@ -429,7 +427,7 @@ def to_gn_args(args): # Enable Vulkan on all platforms except for Android and iOS. This is just # to save on mobile binary size, as there's no reason the Vulkan embedder # features can't work on these platforms. - if args.target_os not in ['android', 'ios', 'wasm']: + if args.target_os not in ['android', 'ios']: gn_args['skia_use_vulkan'] = True gn_args['skia_vulkan_memory_allocator_dir' ] = '//third_party/vulkan_memory_allocator' @@ -569,6 +567,67 @@ def to_gn_args(args): return gn_args +# When building for WASM, almost all GN args used in the Flutter SDK +# build are unused. This method is used instead. +def to_gn_wasm_args(args, gn_args): + gn_args['is_official_build'] = True + gn_args['skia_enable_flutter_defines'] = True + gn_args['is_component_build'] = False + gn_args['use_clang_static_analyzer'] = False + gn_args['is_clang'] = True + gn_args['target_os'] = 'wasm' + gn_args['target_cpu'] = 'wasm' + gn_args['skia_use_angle'] = False + gn_args['skia_use_dng_sdk'] = False + gn_args['skia_use_expat'] = False + gn_args['skia_use_vulkan'] = False + gn_args['skia_use_webgpu'] = False + gn_args['skia_use_libheif'] = False + gn_args['skia_use_libjpeg_turbo_decode'] = True + gn_args['skia_use_libjpeg_turbo_encode'] = False + gn_args['skia_use_libpng_decode'] = True + gn_args['skia_use_libpng_encode'] = True + gn_args['skia_use_libwebp_decode'] = True + gn_args['skia_use_libwebp_encode'] = False + gn_args['skia_use_lua'] = False + gn_args['skia_use_wuffs'] = True + gn_args['skia_use_zlib'] = True + gn_args['skia_gl_standard'] = 'webgl' + gn_args['skia_enable_gpu'] = True + gn_args['skia_enable_sksl_tracing'] = False + gn_args['skia_use_icu'] = True + gn_args['icu_use_data_file'] = False + gn_args['skia_use_freetype'] = True + gn_args['skia_use_harfbuzz'] = True + gn_args['skia_use_fontconfig'] = False + gn_args['skia_use_libheif'] = False + gn_args['skia_enable_fontmgr_custom_directory'] = False + gn_args['skia_enable_fontmgr_custom_embedded'] = True + gn_args['skia_enable_fontmgr_custom_empty'] = False + gn_args['skia_enable_skshaper'] = True + gn_args['skia_enable_skparagraph'] = True + gn_args['skia_canvaskit_force_tracing'] = False + gn_args['skia_canvaskit_enable_skp_serialization'] = True + gn_args['skia_canvaskit_enable_effects_deserialization'] = False + gn_args['skia_canvaskit_enable_skottie'] = False + gn_args['skia_canvaskit_include_viewer'] = False + gn_args['skia_canvaskit_enable_particles'] = False + gn_args['skia_canvaskit_enable_pathops'] = True + gn_args['skia_canvaskit_enable_rt_shader'] = True + gn_args['skia_canvaskit_enable_matrix_helper'] = False + gn_args['skia_canvaskit_enable_canvas_bindings'] = False + gn_args['skia_canvaskit_enable_font'] = True + gn_args['skia_canvaskit_enable_embedded_font'] = True + gn_args['skia_canvaskit_enable_alias_font'] = True + gn_args['skia_canvaskit_legacy_draw_vertices_blend_mode'] = False + gn_args['skia_canvaskit_enable_debugger'] = False + gn_args['skia_canvaskit_enable_paragraph'] = True + gn_args['skia_canvaskit_enable_webgl'] = True + gn_args['skia_canvaskit_enable_webgpu'] = False + is_profile_build = args.runtime_mode == 'profile' or args.runtime_mode == 'debug' + gn_args['skia_canvaskit_profile_build'] = is_profile_build + + def parse_args(args): args = args[1:] parser = argparse.ArgumentParser(description='A script to run `gn gen`.') diff --git a/wasm/BUILD.gn b/wasm/BUILD.gn new file mode 100644 index 0000000000000..7d799fe9186c9 --- /dev/null +++ b/wasm/BUILD.gn @@ -0,0 +1,12 @@ +# Copyright 2022 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. + +# This BUILD.gn is kept separate from //flutter/BUILD.gn because +# //flutter/BUILD.gn pulls in Flutter SDK dependencies which will crash +# when the target CPU is WASM. + +# This is the default target when building when the target CPU is WASM. +group("wasm") { + deps = [ "//third_party/skia/modules/canvaskit" ] +}