diff --git a/.ci.yaml b/.ci.yaml index d84d5a7f1ea34..b6b83d5019c1f 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -53,7 +53,7 @@ platform_properties: ] device_type: none cpu: x86 - os: Mac-12 + os: Mac-12|Mac-13 $flutter/osx_sdk : >- { "sdk_version": "14e300c" @@ -293,6 +293,10 @@ targets: add_recipes_cq: "true" release_build: "true" config_name: linux_host_engine + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"} + ] drone_dimensions: - os=Linux @@ -448,7 +452,7 @@ targets: "sdk_version": "14e300c" } drone_dimensions: - - os=Mac-12 + - os=Mac-12|Mac-13 - name: Linux mac_unopt recipe: engine_v2/engine_v2 @@ -469,7 +473,7 @@ targets: "sdk_version": "14e300c" } drone_dimensions: - - os=Mac-12 + - os=Mac-12|Mac-13 - cpu=x86 - name: Mac impeller-cmake-example diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml deleted file mode 100644 index 191b0ab3b2d14..0000000000000 --- a/.github/workflows/mirror.yml +++ /dev/null @@ -1,28 +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. - -# Mirror master to main branches in the engine repository. -on: - push: - branches: - - 'main' - -# Declare default permissions as read only. -permissions: read-all - -jobs: - mirror_job: - permissions: - pull-requests: write - runs-on: ubuntu-latest - if: ${{ github.repository == 'flutter/engine' }} - name: Mirror main branch to master branch - steps: - - name: Mirror action step - id: mirror - uses: google/mirror-branch-action@30c52ee21f5d3bd7fb28b95501c11aae7f17eebb - with: - github-token: ${{ secrets.FLUTTERMIRRORINGBOT_TOKEN }} - source: 'main' - dest: 'master' diff --git a/DEPS b/DEPS index 582a74f9ea6b5..f61cf9d1437c8 100644 --- a/DEPS +++ b/DEPS @@ -18,7 +18,7 @@ vars = { 'llvm_git': 'https://llvm.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '70634da5c7835efc4d749737f58c9527e1bef5f3', + 'skia_revision': '3b8e2b391d03b118cd7b790d193f012572d3c280', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. @@ -57,28 +57,28 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': 'e6df03a57636ed8a57e580f847460b5683b3a6fb', + 'dart_revision': '3c45f68cd7c27440c7bf5f177c27ecf5f1243c64', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py 'dart_binaryen_rev': 'a51bd6df919a5b79574f0996a760cc20cb05697e', 'dart_boringssl_gen_rev': 'a468ba9fec3f59edf46a7db98caaca893e1e4d96', 'dart_boringssl_rev': '74646566e93de7551bfdfc5f49de7462f13d1d05', - 'dart_browser_launcher_rev': 'c2871b2e03aa4490caf476c274996e2bc20c5451', - 'dart_clock_rev': '200a0209927ea7c4737309e7e9076ec8b97e9c4b', + 'dart_browser_launcher_rev': '4f9e7843b683c9e9357aaf263a929ec6ecf959c8', + 'dart_clock_rev': 'f975668839f45bad561d6227f88297bbbcff03fa', 'dart_collection_rev': 'd27bfaf7994ee690be6ed424b8ee288c7aa672f6', 'dart_devtools_rev': 'f73b42303c9715f5659130f8f1b6dc91fa12d82f', 'dart_libprotobuf_rev': '24487dd1045c7f3d64a21f38a3f0c06cc4cf2edb', 'dart_perfetto_rev': '13ce0c9e13b0940d2476cd0cff2301708a9a2e2b', 'dart_protobuf_gn_rev': 'ca669f79945418f6229e4fef89b666b2a88cbb10', - 'dart_protobuf_rev': '3f567b29cc8979b9e355b746aa57805c08cb72e9', + 'dart_protobuf_rev': 'dcec2eda9db4e6728e900928aa2e46944ba4fa6f', 'dart_pub_rev': 'fca927ae2662204805e1646c0c0687369001a41a', 'dart_root_certificates_rev': '692f6d6488af68e0121317a9c2c9eb393eb0ee50', - 'dart_tools_rev': '01c0b529fb2c1243088239d1ca474fe6d49399f9', - 'dart_watcher_rev': '6ad58dcbbf328fbecf18d6ad2357c67be300b489', - 'dart_webdev_rev': '50534a1299d1fdcac065ac337abcad26886ff029', - 'dart_webkit_inspection_protocol_rev': '82f0c1c46dfdba5edf7c5fa84456233121dd69e1', - 'dart_yaml_edit_rev': 'a7e7fbad5ee263cc681681c1a6eb9e6df5336ad6', + 'dart_tools_rev': 'dd91cb6a2c09c9c499be74ccb8a83cd6f50484bb', + 'dart_watcher_rev': 'b2b278ae4198b4c431a145ddcfdab1460d5f9ec5', + 'dart_webdev_rev': '5ad79c240b000a50057612d6af4573f6e649f65c', + 'dart_webkit_inspection_protocol_rev': '2c6f8b6b7dbcbd207d345ebaa0337a3b8de70244', + 'dart_yaml_edit_rev': '9b9d33c5255798c950e843efe19e6f81a225ad28', 'dart_zlib_rev': '14dd4c4455602c9b71a1a89b5cafd1f4030d2e3f', 'ocmock_rev': 'c4ec0e3a7a9f56cfdbd0aa01f4f97bb4b75c5ef8', # v3.7.1 @@ -334,19 +334,19 @@ deps = { Var('dart_git') + '/args.git@df9b428e53e889835257c8475538e09834ffd022', 'src/third_party/dart/third_party/pkg/async': - Var('dart_git') + '/async.git@def44823a35fc13312d3147cbbd5425a73e7e243', + Var('dart_git') + '/async.git@992457079da78dede35a56257d698288c467dc91', 'src/third_party/dart/third_party/pkg/bazel_worker': Var('dart_git') + '/bazel_worker.git@b1b6a6605f0255eb1bf4aaf5aaf36f0d635e1b20', 'src/third_party/dart/third_party/pkg/boolean_selector': - Var('dart_git') + '/boolean_selector.git@479e1c110355a6c13f88922c9fdec353225fc825', + Var('dart_git') + '/boolean_selector.git@7f523c392056bea55176530a7b1434aa99549e0f', 'src/third_party/dart/third_party/pkg/browser_launcher': Var('dart_git') + '/browser_launcher.git' + '@' + Var('dart_browser_launcher_rev'), 'src/third_party/dart/third_party/pkg/cli_util': - Var('dart_git') + '/cli_util.git@56c1235ba516dbabb3e2b1d4fe76603630f9f5d1', + Var('dart_git') + '/cli_util.git@500dffab6e00332c4c0b814359f06c8a9c3a5573', 'src/third_party/dart/third_party/pkg/clock': Var('dart_git') + '/clock.git' + '@' + Var('dart_clock_rev'), @@ -355,13 +355,13 @@ deps = { Var('dart_git') + '/collection.git' + '@' + Var('dart_collection_rev'), 'src/third_party/dart/third_party/pkg/convert': - Var('dart_git') + '/convert.git@03242b2058af45456e07a5648fe9b9ef40ca57d9', + Var('dart_git') + '/convert.git@f24afa75d065fa3fe70f30300a1570c11e461713', 'src/third_party/dart/third_party/pkg/crypto': - Var('dart_git') + '/crypto.git@36ead7c6f748448cde9149bc96292adb844c4601', + Var('dart_git') + '/crypto.git@f3e64d234416466683e29a4b20cf751684cbae6a', 'src/third_party/dart/third_party/pkg/csslib': - Var('dart_git') + '/csslib.git@f6b68dd9ed9da297f5df4cd31a39787bf35432b3', + Var('dart_git') + '/csslib.git@17346e528b19c09b2d20589e0ffa0f01a5ad54ad', 'src/third_party/dart/third_party/pkg/dart_style': Var('dart_git') + '/dart_style.git@2cee560f2025f8bd5dce3fd5f4c4b5cf5335a10b', @@ -370,22 +370,22 @@ deps = { Var('dart_git') + '/dartdoc.git@53da3e1dd1802c5899352fce251ea0c385a827b0', 'src/third_party/dart/third_party/pkg/ffi': - Var('dart_git') + '/ffi.git@2faec288966d8f564049adb86a7ca43fd6e01fbf', + Var('dart_git') + '/ffi.git@c926657618443ff4821411ede01684096b503f84', 'src/third_party/dart/third_party/pkg/file': - Var('dart_git') + '/external/github.com/google/file.dart@7418131cfe3c5e063166bc3d7cca98985a6d8eeb', + Var('dart_git') + '/external/github.com/google/file.dart@e7c03aa61789cf57063660b99e6310a1f96a3553', 'src/third_party/dart/third_party/pkg/fixnum': - Var('dart_git') + '/fixnum.git@ef45eb556524eadcd72ecdbbed87951288bcd9e7', + Var('dart_git') + '/fixnum.git@3279f5d2a7b01eb315dc0f6cbae57cae8cee35e4', 'src/third_party/dart/third_party/pkg/glob': Var('dart_git') + '/glob.git@00465333cc4110e077cb256b4fa7eff4797bc856', 'src/third_party/dart/third_party/pkg/html': - Var('dart_git') + '/html.git@49e2c8e9b3bc9fcf25a8eb290c026d3c94c5d175', + Var('dart_git') + '/html.git@06bc148600b1d1a70f2256bdf788c213f1f60f55', 'src/third_party/dart/third_party/pkg/http': - Var('dart_git') + '/http.git@7240d0a26c17f06f5e6d704082ea95d10a7a730d', + Var('dart_git') + '/http.git@b9389fea7e52b5b783912f80b17cee429d3b2d62', 'src/third_party/dart/third_party/pkg/http_multi_server': Var('dart_git') + '/http_multi_server.git@03041aabc9ffa4c730c4221bf6ff1ef8bcd27cef', @@ -403,31 +403,31 @@ deps = { Var('dart_git') + '/leak_tracker.git@098bafcf99a5220e3c352d895d991e163568ee03', 'src/third_party/dart/third_party/pkg/logging': - Var('dart_git') + '/logging.git@642ed2124f7ef7abc819a0e22ae0c7afdb5398d3', + Var('dart_git') + '/logging.git@324a0b5fd2b49b80ea4fbe2b48aac7794000e25a', 'src/third_party/dart/third_party/pkg/markdown': - Var('dart_git') + '/markdown.git@4e2e9701d87058311857d06fd7f5df54e8f86c53', + Var('dart_git') + '/markdown.git@efb73b3db22f47925d942d3ad1a0cdcd6dece944', 'src/third_party/dart/third_party/pkg/matcher': - Var('dart_git') + '/matcher.git@7512f8056486f6b0855ec9307ce0f93902c329e7', + Var('dart_git') + '/matcher.git@3d03fa1a3e8f166b9e2ec8557c5d5e83ae1c85bc', 'src/third_party/dart/third_party/pkg/mime': - Var('dart_git') + '/mime.git@af3e5fe753b957e95f03838f8a63782582c413ca', + Var('dart_git') + '/mime.git@8ebf9463a7a230213cee0adc3c019dad78d364ac', 'src/third_party/dart/third_party/pkg/mockito': Var('dart_git') + '/mockito.git@b7d752e7f696ac0a1b1567a7abb8073b9d581a07', 'src/third_party/dart/third_party/pkg/native': - Var('dart_git') + '/native.git@279094d73780f7c128b70f14b7232d5bd478bf2a', + Var('dart_git') + '/native.git@de9d59e4dbdd25321ab59ad5e60a55b197c573e4', 'src/third_party/dart/third_party/pkg/package_config': Var('dart_git') + '/package_config.git@100533d2f836583f281c9dfa11a00d6842c176d4', 'src/third_party/dart/third_party/pkg/path': - Var('dart_git') + '/path.git@4ca27d4e88d47f2d96c3113940a97321b6aa7175', + Var('dart_git') + '/path.git@18ec71f7dde21d8518702d77215fb0b2fa45e970', 'src/third_party/dart/third_party/pkg/pool': - Var('dart_git') + '/pool.git@5ccef15fcd4690d96e22e60c3962f4c97d9430f9', + Var('dart_git') + '/pool.git@c78cef414f86f41879e9eef22caeaf97520a01fe', 'src/third_party/dart/third_party/pkg/protobuf': Var('dart_git') + '/protobuf.git' + '@' + Var('dart_protobuf_rev'), @@ -436,49 +436,49 @@ deps = { Var('dart_git') + '/pub.git' + '@' + Var('dart_pub_rev'), 'src/third_party/dart/third_party/pkg/pub_semver': - Var('dart_git') + '/pub_semver.git@8e5a58fd4231854b35ac585ff81c242885334843', + Var('dart_git') + '/pub_semver.git@f9e94ee38d5dd881afd06308e34dcef717b04e39', 'src/third_party/dart/third_party/pkg/shelf': - Var('dart_git') + '/shelf.git@c15fc6f6ae11079d7796b0bf8c93135a5a112d82', + Var('dart_git') + '/shelf.git@b3adc7c5264b448a77427c6aacd67eedfb16dce2', 'src/third_party/dart/third_party/pkg/source_map_stack_trace': - Var('dart_git') + '/source_map_stack_trace.git@73d449cb90f9faf3ccacde0635f55230c6060024', + Var('dart_git') + '/source_map_stack_trace.git@220962658bf67304207aedc7eeedca6ef64a7c72', 'src/third_party/dart/third_party/pkg/source_maps': - Var('dart_git') + '/source_maps.git@fc6aa16cc3548dec5642057a7fbbce01d64f4a19', + Var('dart_git') + '/source_maps.git@87dc58736b5bd334502005cdbd4d325aba9bc696', 'src/third_party/dart/third_party/pkg/source_span': - Var('dart_git') + '/source_span.git@92e50bf0c15bea00218e5fdb2881d2570de1932b', + Var('dart_git') + '/source_span.git@ed16e0d1323f15e478027430b53e0ef0dbc25a83', 'src/third_party/dart/third_party/pkg/sse': - Var('dart_git') + '/sse.git@37df57d7d09503c12f6200122ab0cb7e92ad50d1', + Var('dart_git') + '/sse.git@8ddb95fbe7c07d91aee92f35f70ac5c839f82c35', 'src/third_party/dart/third_party/pkg/stack_trace': - Var('dart_git') + '/stack_trace.git@634589f915f7b236dba8aca0f581cf792e5a6e03', + Var('dart_git') + '/stack_trace.git@6496ff88cf5c168c2548a454160c27004d176009', 'src/third_party/dart/third_party/pkg/stream_channel': - Var('dart_git') + '/stream_channel.git@ffdb20840d05a276699b50fdfc70cf668bfed6e2', + Var('dart_git') + '/stream_channel.git@178104d0f1316b0120cf0031b8dbae0cbfec4c26', 'src/third_party/dart/third_party/pkg/string_scanner': - Var('dart_git') + '/string_scanner.git@9c525f78fbc4189ee4dc3171a5c79e925b58f58b', + Var('dart_git') + '/string_scanner.git@a7105ef03ed8373c7d995461fcf4994a6f4e781d', 'src/third_party/dart/third_party/pkg/tar': Var('dart_git') + '/external/github.com/simolus3/tar.git@3383397b082c084c327587a293591fac74bc8af3', 'src/third_party/dart/third_party/pkg/term_glyph': - Var('dart_git') + '/term_glyph.git@cff80de129b2e69c11e3a9d7c6ea0447fc37865b', + Var('dart_git') + '/term_glyph.git@7c1eb9d799a3cbbc554c3a283af4d6cee9043297', 'src/third_party/dart/third_party/pkg/test': Var('dart_git') + '/test.git@4341470a2b844cd9a6692647639d652f617dd302', 'src/third_party/dart/third_party/pkg/test_reflective_loader': - Var('dart_git') + '/test_reflective_loader.git@8593eb160f796179f77c8edb6fde050433810211', + Var('dart_git') + '/test_reflective_loader.git@17d40bb06f55f727a79c0f0016d7510294f5db03', 'src/third_party/dart/third_party/pkg/tools': Var('dart_git') + '/tools.git' + '@' + Var('dart_tools_rev'), 'src/third_party/dart/third_party/pkg/typed_data': - Var('dart_git') + '/typed_data.git@d1c15ed29d10568cd713fba77d01c4d79b03ccf8', + Var('dart_git') + '/typed_data.git@0b16bd26c90bc9ac08e8b4b259aa3d7bead34feb', 'src/third_party/dart/third_party/pkg/usage': Var('dart_git') + '/usage.git@d7d2964433f26b9a3c60dc9c6677f00c005ee9fb', @@ -487,7 +487,7 @@ deps = { Var('dart_git') + '/watcher.git' + '@' + Var('dart_watcher_rev'), 'src/third_party/dart/third_party/pkg/web_socket_channel': - Var('dart_git') + '/web_socket_channel.git@f3ac1bf2bd3c93eb6d5d78646ff7de31797f4cf6', + Var('dart_git') + '/web_socket_channel.git@82ac73fef05c474095c740a9525b4cfb61611c3d', 'src/third_party/dart/third_party/pkg/webdev': Var('dart_git') + '/webdev.git' + '@' + Var('dart_webdev_rev'), @@ -496,7 +496,7 @@ deps = { Var('dart_git') + '/external/github.com/google/webkit_inspection_protocol.dart.git' + '@' + Var('dart_webkit_inspection_protocol_rev'), 'src/third_party/dart/third_party/pkg/yaml': - Var('dart_git') + '/yaml.git@9f0d64934c07bc27438074616455618b7103582d', + Var('dart_git') + '/yaml.git@98a3aab54b09d355e094fdb4e5abd9083a2876b8', 'src/third_party/dart/third_party/pkg/yaml_edit': Var('dart_git') + '/yaml_edit.git' + '@' + Var('dart_yaml_edit_rev'), @@ -903,7 +903,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': '32ukjtetQl4pbXfTwkxYvbTCRFCbV67r6Sq-xgtcLFQC' + 'version': 'bhSlAQy4VM3Plrlh42DfEZRCEyUsl7WRUC22wbBSrjYC' } ], 'condition': 'host_os == "mac" and not download_fuchsia_sdk', diff --git a/ci/binary_size_treemap.sh b/ci/binary_size_treemap.sh index b144c4bc153cb..64ca0fac80b6d 100755 --- a/ci/binary_size_treemap.sh +++ b/ci/binary_size_treemap.sh @@ -27,4 +27,9 @@ ADDR2LINE="third_party/android_tools/ndk/toolchains/aarch64-linux-android-4.9/pr # Run the binary size script from the buildroot directory so the treemap path # navigation will start from there. cd "$ENGINE_BUILDROOT" -python3 third_party/dart/runtime/third_party/binary_size/src/run_binary_size_analysis.py --library "$INPUT_PATH" --destdir "$DEST_DIR" --addr2line-binary "$ADDR2LINE" +RUN_BINARY_SIZE_ANALYSIS="third_party/dart/third_party/binary_size/src/run_binary_size_analysis.py" +if [[ ! -f "$RUN_BINARY_SIZE_ANALYSIS" ]]; then + # Fallback to the old path until https://dart-review.googlesource.com/c/sdk/+/332963 rolls to Flutter Engine. + RUN_BINARY_SIZE_ANALYSIS="third_party/dart/runtime/third_party/binary_size/src/run_binary_size_analysis.py" +fi +python3 "$RUN_BINARY_SIZE_ANALYSIS" --library "$INPUT_PATH" --destdir "$DEST_DIR" --addr2line-binary "$ADDR2LINE" diff --git a/ci/builders/linux_host_engine.json b/ci/builders/linux_host_engine.json index aae0ac2835190..8c1f46f614f84 100644 --- a/ci/builders/linux_host_engine.json +++ b/ci/builders/linux_host_engine.json @@ -185,6 +185,12 @@ "device_type=none", "os=Linux" ], + "dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" + } + ], "gclient_variables": { "download_android_deps": false }, diff --git a/ci/builders/mac_android_aot_engine.json b/ci/builders/mac_android_aot_engine.json index f59354f1078b5..962250d0de328 100644 --- a/ci/builders/mac_android_aot_engine.json +++ b/ci/builders/mac_android_aot_engine.json @@ -14,7 +14,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gclient_variables": { @@ -53,7 +53,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gclient_variables": { @@ -93,7 +93,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gclient_variables": { @@ -133,7 +133,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gclient_variables": { @@ -172,7 +172,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gclient_variables": { @@ -212,7 +212,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gclient_variables": { diff --git a/ci/builders/mac_clang_tidy.json b/ci/builders/mac_clang_tidy.json index f68aa4028f3b8..4e3fe0ccaadd2 100644 --- a/ci/builders/mac_clang_tidy.json +++ b/ci/builders/mac_clang_tidy.json @@ -3,7 +3,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -24,7 +24,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -50,7 +50,7 @@ "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -82,7 +82,7 @@ "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -114,7 +114,7 @@ "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -146,7 +146,7 @@ "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -178,7 +178,7 @@ "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { diff --git a/ci/builders/mac_clang_tidy_presubmit.json b/ci/builders/mac_clang_tidy_presubmit.json index 19f030435649e..50deb4cd15d20 100644 --- a/ci/builders/mac_clang_tidy_presubmit.json +++ b/ci/builders/mac_clang_tidy_presubmit.json @@ -3,7 +3,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -24,7 +24,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -50,7 +50,7 @@ "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -82,7 +82,7 @@ "recipe": "engine_v2/tester_engine", "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { diff --git a/ci/builders/mac_host_engine.json b/ci/builders/mac_host_engine.json index 83361f31642a3..8642c1b3e82db 100644 --- a/ci/builders/mac_host_engine.json +++ b/ci/builders/mac_host_engine.json @@ -15,7 +15,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86", "mac_model=Macmini8,1" ], @@ -79,7 +79,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86", "mac_model=Macmini8,1" ], @@ -139,7 +139,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86", "mac_model=Macmini8,1" ], @@ -208,7 +208,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gclient_variables": { @@ -255,7 +255,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gclient_variables": { @@ -299,7 +299,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gclient_variables": { diff --git a/ci/builders/mac_impeller_cmake_example.json b/ci/builders/mac_impeller_cmake_example.json index 5a9596d4f9bab..291a4a2755709 100644 --- a/ci/builders/mac_impeller_cmake_example.json +++ b/ci/builders/mac_impeller_cmake_example.json @@ -5,7 +5,7 @@ "archives": [], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { diff --git a/ci/builders/mac_ios_engine.json b/ci/builders/mac_ios_engine.json index 3dfc5c4d559b4..08cc3adf97dc8 100644 --- a/ci/builders/mac_ios_engine.json +++ b/ci/builders/mac_ios_engine.json @@ -3,7 +3,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gn": [ @@ -26,7 +26,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gn": [ @@ -50,7 +50,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gn": [ @@ -74,7 +74,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gn": [ @@ -99,7 +99,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gn": [ @@ -124,7 +124,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gn": [ @@ -148,7 +148,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gn": [ @@ -173,7 +173,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gn": [ @@ -198,7 +198,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gn": [ @@ -224,7 +224,7 @@ { "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gn": [ diff --git a/ci/builders/mac_unopt.json b/ci/builders/mac_unopt.json index 480b96a5c699d..ae72400e34ef2 100644 --- a/ci/builders/mac_unopt.json +++ b/ci/builders/mac_unopt.json @@ -11,7 +11,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86", "mac_model=Macmini8,1" ], @@ -71,7 +71,7 @@ }, "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=x86" ], "gclient_variables": { @@ -128,7 +128,7 @@ ], "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -177,7 +177,7 @@ }, "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { @@ -247,7 +247,7 @@ }, "drone_dimensions": [ "device_type=none", - "os=Mac-12", + "os=Mac-12|Mac-13", "cpu=arm64" ], "gclient_variables": { diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index f37b6d57a239e..1551a8af648cf 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -2835,6 +2835,7 @@ ../../../third_party/skia/modules/skparagraph/src/BUILD.bazel ../../../third_party/skia/modules/skparagraph/tests ../../../third_party/skia/modules/skparagraph/utils/BUILD.bazel +../../../third_party/skia/modules/skplaintexteditor/README.md ../../../third_party/skia/modules/skresources/BUILD.bazel ../../../third_party/skia/modules/skresources/include/BUILD.bazel ../../../third_party/skia/modules/skresources/src/BUILD.bazel diff --git a/ci/licenses_golden/licenses_dart b/ci/licenses_golden/licenses_dart index 36ebb4b3d3a69..1c2a2307d4376 100644 --- a/ci/licenses_golden/licenses_dart +++ b/ci/licenses_golden/licenses_dart @@ -1,4 +1,4 @@ -Signature: 4f6c6ef3539e00d6683e50d9e5058131 +Signature: 2dc685e6d11b4532d44929ab582acbeb ==================================================================================================== LIBRARY: dart diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 7f1c4a9596685..83e563036672d 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 6ea751a5422bf58307b04f4f08cc2a6b +Signature: c862078ded37d93a64ca7009935664e0 ==================================================================================================== LIBRARY: etc1 @@ -386,6 +386,7 @@ FILE: ../../../third_party/skia/modules/pathkit/perf/pathops.bench.js FILE: ../../../third_party/skia/modules/pathkit/perf/perfReporter.js FILE: ../../../third_party/skia/modules/skparagraph/test.html FILE: ../../../third_party/skia/package-lock.json +FILE: ../../../third_party/skia/relnotes/FontConfigInterface.md FILE: ../../../third_party/skia/src/gpu/gpu_workaround_list.txt FILE: ../../../third_party/skia/src/ports/fontations/Cargo.toml FILE: ../../../third_party/skia/src/sksl/generated/sksl_compute.minified.sksl @@ -6195,6 +6196,16 @@ ORIGIN: ../../../third_party/skia/gm/swizzle.cpp + ../../../third_party/skia/LIC ORIGIN: ../../../third_party/skia/gm/tilemodes_alpha.cpp + ../../../third_party/skia/LICENSE ORIGIN: ../../../third_party/skia/include/core/SkPathTypes.h + ../../../third_party/skia/LICENSE ORIGIN: ../../../third_party/skia/modules/skparagraph/bench/ParagraphBench.cpp + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/skplaintexteditor/app/editor_application.cpp + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/skplaintexteditor/include/editor.h + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/skplaintexteditor/include/stringslice.h + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/skplaintexteditor/include/stringview.h + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/skplaintexteditor/src/editor.cpp + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/skplaintexteditor/src/shape.cpp + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/skplaintexteditor/src/shape.h + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/skplaintexteditor/src/stringslice.cpp + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/skplaintexteditor/src/word_boundaries.cpp + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/skplaintexteditor/src/word_boundaries.h + ../../../third_party/skia/LICENSE ORIGIN: ../../../third_party/skia/src/base/SkContainers.cpp + ../../../third_party/skia/LICENSE ORIGIN: ../../../third_party/skia/src/base/SkMalloc.cpp + ../../../third_party/skia/LICENSE ORIGIN: ../../../third_party/skia/src/core/SkPixelRefPriv.h + ../../../third_party/skia/LICENSE @@ -6224,6 +6235,16 @@ FILE: ../../../third_party/skia/gm/swizzle.cpp FILE: ../../../third_party/skia/gm/tilemodes_alpha.cpp FILE: ../../../third_party/skia/include/core/SkPathTypes.h FILE: ../../../third_party/skia/modules/skparagraph/bench/ParagraphBench.cpp +FILE: ../../../third_party/skia/modules/skplaintexteditor/app/editor_application.cpp +FILE: ../../../third_party/skia/modules/skplaintexteditor/include/editor.h +FILE: ../../../third_party/skia/modules/skplaintexteditor/include/stringslice.h +FILE: ../../../third_party/skia/modules/skplaintexteditor/include/stringview.h +FILE: ../../../third_party/skia/modules/skplaintexteditor/src/editor.cpp +FILE: ../../../third_party/skia/modules/skplaintexteditor/src/shape.cpp +FILE: ../../../third_party/skia/modules/skplaintexteditor/src/shape.h +FILE: ../../../third_party/skia/modules/skplaintexteditor/src/stringslice.cpp +FILE: ../../../third_party/skia/modules/skplaintexteditor/src/word_boundaries.cpp +FILE: ../../../third_party/skia/modules/skplaintexteditor/src/word_boundaries.h FILE: ../../../third_party/skia/src/base/SkContainers.cpp FILE: ../../../third_party/skia/src/base/SkMalloc.cpp FILE: ../../../third_party/skia/src/core/SkPixelRefPriv.h diff --git a/impeller/geometry/geometry_asserts.h b/impeller/geometry/geometry_asserts.h index 57022b461f9a0..ab3f37389a522 100644 --- a/impeller/geometry/geometry_asserts.h +++ b/impeller/geometry/geometry_asserts.h @@ -52,10 +52,10 @@ inline ::testing::AssertionResult QuaternionNear(impeller::Quaternion a, } inline ::testing::AssertionResult RectNear(impeller::Rect a, impeller::Rect b) { - auto equal = NumberNear(a.origin.x, b.origin.x) && - NumberNear(a.origin.y, b.origin.y) && - NumberNear(a.size.width, b.size.width) && - NumberNear(a.size.height, b.size.height); + auto equal = NumberNear(a.GetOrigin().x, b.GetOrigin().x) && + NumberNear(a.GetOrigin().y, b.GetOrigin().y) && + NumberNear(a.GetSize().width, b.GetSize().width) && + NumberNear(a.GetSize().height, b.GetSize().height); return equal ? ::testing::AssertionSuccess() : ::testing::AssertionFailure() << "Rects are not equal."; diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc index 6dc6bd0f741f0..d5ee6d423002d 100644 --- a/impeller/geometry/geometry_unittests.cc +++ b/impeller/geometry/geometry_unittests.cc @@ -744,10 +744,10 @@ TEST(GeometryTest, CanConvertTTypesExplicitly) { { Rect r1 = Rect::MakeXYWH(1.0, 2.0, 3.0, 4.0); IRect r2 = static_cast(r1); - ASSERT_EQ(r2.origin.x, 1u); - ASSERT_EQ(r2.origin.y, 2u); - ASSERT_EQ(r2.size.width, 3u); - ASSERT_EQ(r2.size.height, 4u); + ASSERT_EQ(r2.GetOrigin().x, 1u); + ASSERT_EQ(r2.GetOrigin().y, 2u); + ASSERT_EQ(r2.GetSize().width, 3u); + ASSERT_EQ(r2.GetSize().height, 4u); } } diff --git a/impeller/geometry/rect.h b/impeller/geometry/rect.h index 3f6cdceb04910..c40ce26ad1541 100644 --- a/impeller/geometry/rect.h +++ b/impeller/geometry/rect.h @@ -20,7 +20,9 @@ template struct TRect { using Type = T; + /// DEPRECATED: Use |GetOrigin| TPoint origin; + /// DEPRECATED: Use |GetSize| TSize size; constexpr TRect() : origin({0, 0}), size({0, 0}) {} diff --git a/impeller/golden_tests_harvester/bin/golden_tests_harvester.dart b/impeller/golden_tests_harvester/bin/golden_tests_harvester.dart index 21524bf9bc4da..62a5f4525d856 100644 --- a/impeller/golden_tests_harvester/bin/golden_tests_harvester.dart +++ b/impeller/golden_tests_harvester/bin/golden_tests_harvester.dart @@ -16,13 +16,16 @@ bool get isLuciEnv => Platform.environment.containsKey(_kLuciEnvName); /// Fake SkiaGoldClient that is used if the harvester is run outside of Luci. class FakeSkiaGoldClient implements SkiaGoldClient { - FakeSkiaGoldClient(this._workingDirectory, {this.dimensions}); + FakeSkiaGoldClient(this._workingDirectory, {this.dimensions, this.verbose = false}); final Directory _workingDirectory; @override final Map? dimensions; + @override + final bool verbose; + @override Future addImg(String testName, File goldenFile, {double differentPixelsRate = 0.01, diff --git a/impeller/golden_tests_harvester/pubspec.yaml b/impeller/golden_tests_harvester/pubspec.yaml index d733c8c162583..02d10c2c6434f 100644 --- a/impeller/golden_tests_harvester/pubspec.yaml +++ b/impeller/golden_tests_harvester/pubspec.yaml @@ -30,6 +30,8 @@ dependency_overrides: path: ../../../third_party/dart/third_party/pkg/file/packages/file meta: path: ../../../third_party/dart/pkg/meta + engine_repo_tools: + path: ../../tools/pkg/engine_repo_tools path: path: ../../../third_party/dart/third_party/pkg/path platform: diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 4c9673e478fdb..57f454e04fd51 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -164,6 +164,7 @@ source_set("ui") { "//flutter/shell/common:display", "//flutter/shell/common:platform_message_handler", "//flutter/third_party/txt", + "//third_party/skia/modules/skparagraph", ] deps = [ diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 310de114e377a..6b048b4af3596 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -218,6 +218,9 @@ typedef CanvasPath Path; V(Paragraph, didExceedMaxLines, 1) \ V(Paragraph, dispose, 1) \ V(Paragraph, getLineBoundary, 2) \ + V(Paragraph, getLineMetricsAt, 3) \ + V(Paragraph, getLineNumberAt, 2) \ + V(Paragraph, getNumberOfLines, 1) \ V(Paragraph, getPositionForOffset, 3) \ V(Paragraph, getRectsForPlaceholders, 1) \ V(Paragraph, getRectsForRange, 5) \ diff --git a/lib/ui/text.dart b/lib/ui/text.dart index 8bf5a8ccec707..276c326106dc3 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -2772,6 +2772,18 @@ class LineMetrics { required this.lineNumber, }); + LineMetrics._( + this.hardBreak, + this.ascent, + this.descent, + this.unscaledAscent, + this.height, + this.width, + this.left, + this.baseline, + this.lineNumber, + ); + /// True if this line ends with an explicit line break (e.g. '\n') or is the end /// of the paragraph. False otherwise. final bool hardBreak; @@ -2992,6 +3004,32 @@ abstract class Paragraph { /// to repeatedly call this. Instead, cache the results. List computeLineMetrics(); + /// Returns the [LineMetrics] for the line at `lineNumber`, or null if the + /// given `lineNumber` is greater than or equal to [numberOfLines]. + LineMetrics? getLineMetricsAt(int lineNumber); + + /// The total number of visible lines in the paragraph. + /// + /// Returns a non-negative number. If `maxLines` is non-null, the value of + /// [numberOfLines] never exceeds `maxLines`. + int get numberOfLines; + + /// Returns the line number of the line that contains the code unit that + /// `codeUnitOffset` points to. + /// + /// This method returns null if the given `codeUnitOffset` is out of bounds, or + /// is logically after the last visible codepoint. This includes the case where + /// its codepoint belongs to a visible line, but the text layout library + /// replaced it with an ellipsis. + /// + /// If the target code unit points to a control character that introduces + /// mandatory line breaks (most notably the line feed character `LF`, typically + /// represented in strings as the escape sequence "\n"), to conform to + /// [the unicode rules](https://unicode.org/reports/tr14/#LB4), the control + /// character itself is always considered to be at the end of "current" line + /// rather than the beginning of the new line. + int? getLineNumberAt(int codeUnitOffset); + /// Release the resources used by this object. The object is no longer usable /// after this method is called. void dispose(); @@ -3169,6 +3207,23 @@ base class _NativeParagraph extends NativeFieldWrapperClass1 implements Paragrap @Native)>(symbol: 'Paragraph::computeLineMetrics') external Float64List _computeLineMetrics(); + @override + LineMetrics? getLineMetricsAt(int lineNumber) => _getLineMetricsAt(lineNumber, LineMetrics._); + @Native, Uint32, Handle)>(symbol: 'Paragraph::getLineMetricsAt') + external LineMetrics? _getLineMetricsAt(int lineNumber, Function constructor); + + @override + @Native)>(symbol: 'Paragraph::getNumberOfLines') + external int get numberOfLines; + + @override + int? getLineNumberAt(int codeUnitOffset) { + final int lineNumber = _getLineNumber(codeUnitOffset); + return lineNumber < 0 ? null : lineNumber; + } + @Native, Uint32)>(symbol: 'Paragraph::getLineNumberAt') + external int _getLineNumber(int codeUnitOffset); + @override void dispose() { assert(!_disposed); diff --git a/lib/ui/text/paragraph.cc b/lib/ui/text/paragraph.cc index 624ed145dcf38..fedf84e02c7de 100644 --- a/lib/ui/text/paragraph.cc +++ b/lib/ui/text/paragraph.cc @@ -8,10 +8,14 @@ #include "flutter/common/task_runners.h" #include "flutter/fml/logging.h" #include "flutter/fml/task_runner.h" +#include "third_party/dart/runtime/include/dart_api.h" +#include "third_party/skia/modules/skparagraph/include/DartTypes.h" +#include "third_party/skia/modules/skparagraph/include/Paragraph.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" #include "third_party/tonic/dart_binding_macros.h" #include "third_party/tonic/dart_library_natives.h" +#include "third_party/tonic/logging/dart_invoke.h" namespace flutter { @@ -122,12 +126,12 @@ Dart_Handle Paragraph::getWordBoundary(unsigned offset) { return tonic::DartConverter::ToDart(result); } -Dart_Handle Paragraph::getLineBoundary(unsigned offset) { +Dart_Handle Paragraph::getLineBoundary(unsigned utf16Offset) { std::vector metrics = m_paragraph->GetLineMetrics(); int line_start = -1; int line_end = -1; for (txt::LineMetrics& line : metrics) { - if (offset >= line.start_index && offset <= line.end_index) { + if (utf16Offset >= line.start_index && utf16Offset <= line.end_index) { line_start = line.start_index; line_end = line.end_index; break; @@ -137,7 +141,7 @@ Dart_Handle Paragraph::getLineBoundary(unsigned offset) { return tonic::DartConverter::ToDart(result); } -tonic::Float64List Paragraph::computeLineMetrics() { +tonic::Float64List Paragraph::computeLineMetrics() const { std::vector metrics = m_paragraph->GetLineMetrics(); // Layout: @@ -165,6 +169,42 @@ tonic::Float64List Paragraph::computeLineMetrics() { return result; } +Dart_Handle Paragraph::getLineMetricsAt(int lineNumber, + Dart_Handle constructor) const { + skia::textlayout::LineMetrics line; + const bool found = m_paragraph->GetLineMetricsAt(lineNumber, &line); + if (!found) { + return Dart_Null(); + } + std::array arguments = { + Dart_NewBoolean(line.fHardBreak), + Dart_NewDouble(line.fAscent), + Dart_NewDouble(line.fDescent), + Dart_NewDouble(line.fUnscaledAscent), + // We add then round to get the height. The + // definition of height here is different + // than the one in LibTxt. + Dart_NewDouble(round(line.fAscent + line.fDescent)), + Dart_NewDouble(line.fWidth), + Dart_NewDouble(line.fLeft), + Dart_NewDouble(line.fBaseline), + Dart_NewInteger(line.fLineNumber), + }; + + Dart_Handle handle = + Dart_InvokeClosure(constructor, arguments.size(), arguments.data()); + tonic::CheckAndHandleError(handle); + return handle; +} + +size_t Paragraph::getNumberOfLines() const { + return m_paragraph->GetNumberOfLines(); +} + +int Paragraph::getLineNumberAt(size_t utf16Offset) const { + return m_paragraph->GetLineNumberAt(utf16Offset); +} + void Paragraph::dispose() { m_paragraph.reset(); ClearDartWrapper(); diff --git a/lib/ui/text/paragraph.h b/lib/ui/text/paragraph.h index 1a5d8217aafc4..311c33a7fc17e 100644 --- a/lib/ui/text/paragraph.h +++ b/lib/ui/text/paragraph.h @@ -47,7 +47,10 @@ class Paragraph : public RefCountedDartWrappable { Dart_Handle getPositionForOffset(double dx, double dy); Dart_Handle getWordBoundary(unsigned offset); Dart_Handle getLineBoundary(unsigned offset); - tonic::Float64List computeLineMetrics(); + tonic::Float64List computeLineMetrics() const; + Dart_Handle getLineMetricsAt(int lineNumber, Dart_Handle constructor) const; + size_t getNumberOfLines() const; + int getLineNumberAt(size_t utf16Offset) const; void dispose(); diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index dff531d06c244..bc988188b46d9 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -3237,6 +3237,18 @@ extension SkParagraphExtension on SkParagraph { List getLineMetrics() => _getLineMetrics().toDart.cast(); + @JS('getLineMetricsAt') + external SkLineMetrics? _getLineMetricsAt(JSNumber index); + SkLineMetrics? getLineMetricsAt(double index) => _getLineMetricsAt(index.toJS); + + @JS('getNumberOfLines') + external JSNumber _getNumberOfLines(); + double getNumberOfLines() => _getNumberOfLines().toDartDouble; + + @JS('getLineNumberAt') + external JSNumber _getLineNumberAt(JSNumber index); + double getLineNumberAt(double index) => _getLineNumberAt(index.toJS).toDartDouble; + @JS('getLongestLine') external JSNumber _getLongestLine(); double getLongestLine() => _getLongestLine().toDartDouble; diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 0948916b5d799..6284ffe55bd37 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -728,6 +728,26 @@ class CkParagraph implements ui.Paragraph { return result; } + @override + ui.LineMetrics? getLineMetricsAt(int lineNumber) { + assert(!_disposed, 'Paragraph has been disposed.'); + final SkLineMetrics? metrics = skiaObject.getLineMetricsAt(lineNumber.toDouble()); + return metrics == null ? null : CkLineMetrics._(metrics); + } + + @override + int get numberOfLines { + assert(!_disposed, 'Paragraph has been disposed.'); + return skiaObject.getNumberOfLines().toInt(); + } + + @override + int? getLineNumberAt(int codeUnitOffset) { + assert(!_disposed, 'Paragraph has been disposed.'); + final int lineNumber = skiaObject.getLineNumberAt(codeUnitOffset.toDouble()).toInt(); + return lineNumber >= 0 ? lineNumber : null; + } + bool _disposed = false; @override diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index c00c1bcac9b17..af721f72b2864 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -101,6 +101,15 @@ class SkwasmParagraph extends SkwasmObjectWrapper implements ui.Pa @override bool get didExceedMaxLines => paragraphGetDidExceedMaxLines(handle); + @override + int get numberOfLines => paragraphGetLineCount(handle); + + @override + int? getLineNumberAt(int codeUnitOffset) { + final int lineNumber = paragraphGetLineNumberAt(handle, codeUnitOffset); + return lineNumber >= 0 ? lineNumber : null; + } + @override void layout(ui.ParagraphConstraints constraints) { paragraphLayout(handle, constraints.width); @@ -214,6 +223,12 @@ class SkwasmParagraph extends SkwasmObjectWrapper implements ui.Pa (int index) => SkwasmLineMetrics._(paragraphGetLineMetricsAtIndex(handle, index)) ); } + + @override + ui.LineMetrics? getLineMetricsAt(int index) { + final LineMetricsHandle lineMetrics = paragraphGetLineMetricsAtIndex(handle, index); + return lineMetrics == nullptr ? SkwasmLineMetrics._(lineMetrics) : null; + } } void withScopedFontList( diff --git a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart index f527d576bc41b..c749a8512d38c 100644 --- a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart @@ -40,7 +40,7 @@ class CanvasParagraph implements ui.Paragraph { final EngineParagraphStyle paragraphStyle; /// The full textual content of the paragraph. - late String plainText; + final String plainText; /// Whether this paragraph can be drawn on a bitmap canvas. /// @@ -221,17 +221,12 @@ class CanvasParagraph implements ui.Paragraph { @override ui.TextRange getLineBoundary(ui.TextPosition position) { - final int index = position.offset; - - int i; - for (i = 0; i < lines.length - 1; i++) { - final ParagraphLine line = lines[i]; - if (index >= line.startIndex && index < line.endIndex) { - break; - } + if (lines.isEmpty) { + return ui.TextRange.empty; } - - final ParagraphLine line = lines[i]; + final int? lineNumber = getLineNumberAt(position.offset); + // Fallback to the last line for backward compatibility. + final ParagraphLine line = lineNumber != null ? lines[lineNumber] : lines.last; return ui.TextRange(start: line.startIndex, end: line.endIndex - line.trailingNewlines); } @@ -240,6 +235,32 @@ class CanvasParagraph implements ui.Paragraph { return lines.map((ParagraphLine line) => line.lineMetrics).toList(); } + @override + EngineLineMetrics? getLineMetricsAt(int lineNumber) { + return 0 <= lineNumber && lineNumber < lines.length + ? lines[lineNumber].lineMetrics + : null; + } + + @override + int get numberOfLines => lines.length; + + @override + int? getLineNumberAt(int codeUnitOffset) => _findLine(codeUnitOffset, 0, lines.length); + + int? _findLine(int codeUnitOffset, int startLine, int endLine) { + if (endLine <= startLine || codeUnitOffset < lines[startLine].startIndex || lines[endLine - 1].endIndex <= codeUnitOffset) { + return null; + } + if (endLine == startLine + 1) { + return startLine; + } + // endLine >= startLine + 2 thus we have + // startLine + 1 <= midIndex <= endLine - 1 + final int midIndex = (startLine + endLine) ~/ 2; + return _findLine(codeUnitOffset, midIndex, endLine) ?? _findLine(codeUnitOffset, startLine, midIndex); + } + bool _disposed = false; @override diff --git a/lib/web_ui/lib/src/engine/text/layout_service.dart b/lib/web_ui/lib/src/engine/text/layout_service.dart index ae46ae685689f..d03a70b91883d 100644 --- a/lib/web_ui/lib/src/engine/text/layout_service.dart +++ b/lib/web_ui/lib/src/engine/text/layout_service.dart @@ -390,7 +390,10 @@ class TextLayoutService { // it possible to do hit testing. Once we find the box, we look inside that // box to find where exactly the `offset` is located. - final ParagraphLine line = _findLineForY(offset.dy); + final ParagraphLine? line = _findLineForY(offset.dy); + if (line == null) { + return const ui.TextPosition(offset: 0); + } // [offset] is to the left of the line. if (offset.dx <= line.left) { return ui.TextPosition( @@ -416,7 +419,10 @@ class TextLayoutService { return ui.TextPosition(offset: line.startIndex); } - ParagraphLine _findLineForY(double y) { + ParagraphLine? _findLineForY(double y) { + if (lines.isEmpty) { + return null; + } // We could do a binary search here but it's not worth it because the number // of line is typically low, and each iteration is a cheap comparison of // doubles. diff --git a/lib/web_ui/lib/text.dart b/lib/web_ui/lib/text.dart index 5d39948ea83c3..ba09ee5068af4 100644 --- a/lib/web_ui/lib/text.dart +++ b/lib/web_ui/lib/text.dart @@ -695,6 +695,9 @@ abstract class Paragraph { TextRange getLineBoundary(TextPosition position); List getBoxesForPlaceholders(); List computeLineMetrics(); + LineMetrics? getLineMetricsAt(int lineNumber); + int get numberOfLines; + int? getLineNumberAt(int codeUnitOffset); void dispose(); bool get debugDisposed; } diff --git a/lib/web_ui/pubspec.yaml b/lib/web_ui/pubspec.yaml index 1b53ead9bd9c8..e777c0ef26603 100644 --- a/lib/web_ui/pubspec.yaml +++ b/lib/web_ui/pubspec.yaml @@ -54,3 +54,7 @@ dev_dependencies: path: ../../web_sdk/web_engine_tester skia_gold_client: path: ../../testing/skia_gold_client + +dependency_overrides: + engine_repo_tools: + path: ../../tools/pkg/engine_repo_tools diff --git a/lib/web_ui/skwasm/text/paragraph.cpp b/lib/web_ui/skwasm/text/paragraph.cpp index dcd1cd2e01b83..a44baf4d1ac39 100644 --- a/lib/web_ui/skwasm/text/paragraph.cpp +++ b/lib/web_ui/skwasm/text/paragraph.cpp @@ -74,14 +74,18 @@ SKWASM_EXPORT size_t paragraph_getLineCount(Paragraph* paragraph) { SKWASM_EXPORT int paragraph_getLineNumberAt(Paragraph* paragraph, size_t characterIndex) { - return paragraph->getLineNumberAt(characterIndex); + return paragraph->getLineNumberAtUTF16Offset(characterIndex); } SKWASM_EXPORT LineMetrics* paragraph_getLineMetricsAtIndex(Paragraph* paragraph, - size_t index) { + size_t lineNumber) { auto metrics = new LineMetrics(); - paragraph->getLineMetricsAt(index, metrics); - return metrics; + if (paragraph->getLineMetricsAt(lineNumber, metrics)) { + return metrics; + } else { + delete metrics; + return nullptr; + } } struct TextBoxList { diff --git a/lib/web_ui/test/canvaskit/text_test.dart b/lib/web_ui/test/canvaskit/text_test.dart index 244614594daa0..2c788cffd2bcd 100644 --- a/lib/web_ui/test/canvaskit/text_test.dart +++ b/lib/web_ui/test/canvaskit/text_test.dart @@ -124,6 +124,43 @@ void testMain() { }); }); + test('empty paragraph', () { + const double fontSize = 10.0; + final ui.Paragraph paragraph = ui.ParagraphBuilder(CkParagraphStyle( + fontSize: fontSize, + )).build(); + paragraph.layout(const ui.ParagraphConstraints(width: double.infinity)); + + expect(paragraph.getLineMetricsAt(0), isNull); + expect(paragraph.numberOfLines, 0); + expect(paragraph.getLineNumberAt(0), isNull); + }); + + test('Basic line related metrics', () { + const double fontSize = 10; + final ui.ParagraphBuilder builder = ui.ParagraphBuilder(CkParagraphStyle( + fontStyle: ui.FontStyle.normal, + fontWeight: ui.FontWeight.normal, + fontSize: fontSize, + maxLines: 1, + ellipsis: 'BBB', + ))..addText('A' * 100); + final ui.Paragraph paragraph = builder.build(); + paragraph.layout(const ui.ParagraphConstraints(width: 100.0)); + + expect(paragraph.numberOfLines, 1); + + expect(paragraph.getLineMetricsAt(-1), isNull); + expect(paragraph.getLineMetricsAt(0), isNotNull); + expect(paragraph.getLineMetricsAt(1), isNull); + + expect(paragraph.getLineNumberAt(-1), isNull); + expect(paragraph.getLineNumberAt(0), 0); + expect(paragraph.getLineNumberAt(6), 0); + // The last 3 characters on the first line are ellipsized with BBB. + expect(paragraph.getLineMetricsAt(7), isNull); + }); + test('rounding hack disabled by default', () { expect(ui.ParagraphBuilder.shouldDisableRoundingHack, isTrue); diff --git a/lib/web_ui/test/html/text_test.dart b/lib/web_ui/test/html/text_test.dart index d9c7780fde69c..fc0db1f58d3f4 100644 --- a/lib/web_ui/test/html/text_test.dart +++ b/lib/web_ui/test/html/text_test.dart @@ -79,10 +79,6 @@ Future testMain() async { expect(paragraph.height, fontSize * 2.0); // because it wraps expect(paragraph.width, fontSize * 5.0); expect(paragraph.minIntrinsicWidth, fontSize * 4.0); - - // TODO(yjbanov): due to https://github.com/flutter/flutter/issues/21965 - // Flutter reports a different number. Ours is correct - // though. expect(paragraph.maxIntrinsicWidth, fontSize * 9.0); expect(paragraph.alphabeticBaseline, fontSize * .8); expect( @@ -94,6 +90,31 @@ Future testMain() async { } }); + test('Basic line related metrics', () { + const double fontSize = 10; + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle( + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: fontSize, + maxLines: 1, + ellipsis: 'BBB', + ))..addText('A' * 100); + final Paragraph paragraph = builder.build(); + paragraph.layout(const ParagraphConstraints(width: 100.0)); + + expect(paragraph.numberOfLines, 1); + + expect(paragraph.getLineMetricsAt(-1), isNull); + expect(paragraph.getLineMetricsAt(0), isNotNull); + expect(paragraph.getLineMetricsAt(1), isNull); + + expect(paragraph.getLineNumberAt(-1), isNull); + expect(paragraph.getLineNumberAt(0), 0); + expect(paragraph.getLineNumberAt(6), 0); + // The last 3 characters on the first line are ellipsized with BBB. + expect(paragraph.getLineNumberAt(7), isNull); + }); + test('Can disable rounding hack', () { if (!ParagraphBuilder.shouldDisableRoundingHack) { ParagraphBuilder.setDisableRoundingHack(true); diff --git a/testing/dart/BUILD.gn b/testing/dart/BUILD.gn index d9bd962168333..8d590a83eeede 100644 --- a/testing/dart/BUILD.gn +++ b/testing/dart/BUILD.gn @@ -48,9 +48,11 @@ tests = [ ] foreach(test, tests) { + skia_gold_work_dir = rebase_path("$root_gen_dir/skia_gold_$test") flutter_frontend_server("compile_$test") { main_dart = test kernel_output = "$root_gen_dir/$test.dill" + extra_args = [ "-DkSkiaGoldWorkDirectory=$skia_gold_work_dir" ] package_config = ".dart_tool/package_config.json" } } diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart index d317e2f13e420..8c4937bdab2d1 100644 --- a/testing/dart/canvas_test.dart +++ b/testing/dart/canvas_test.dart @@ -12,6 +12,7 @@ import 'package:litetest/litetest.dart'; import 'package:path/path.dart' as path; import 'package:vector_math/vector_math_64.dart'; +import 'goldens.dart'; import 'impeller_enabled.dart'; typedef CanvasCallback = void Function(Canvas canvas); @@ -123,59 +124,9 @@ void testNoCrashes() { }); } -/// @returns true When the images are reasonably similar. -/// @todo Make the search actually fuzzy to a certain degree. -Future fuzzyCompareImages(Image golden, Image img) async { - if (golden.width != img.width || golden.height != img.height) { - return false; - } - int getPixel(ByteData data, int x, int y) => data.getUint32((x + y * golden.width) * 4); - final ByteData goldenData = (await golden.toByteData())!; - final ByteData imgData = (await img.toByteData())!; - for (int y = 0; y < golden.height; y++) { - for (int x = 0; x < golden.width; x++) { - if (getPixel(goldenData, x, y) != getPixel(imgData, x, y)) { - return false; - } - } - } - return true; -} - -Future saveTestImage(Image image, String filename) async { - final String imagesPath = path.join('flutter', 'testing', 'resources'); - final ByteData pngData = (await image.toByteData(format: ImageByteFormat.png))!; - final String outPath = path.join(imagesPath, filename); - File(outPath).writeAsBytesSync(pngData.buffer.asUint8List()); - print('wrote: $outPath'); -} - -/// @returns true When the images are reasonably similar. -Future fuzzyGoldenImageCompare( - Image image, String goldenImageName) async { - final String imagesPath = path.join('flutter', 'testing', 'resources'); - final File file = File(path.join(imagesPath, goldenImageName)); - - bool areEqual = false; - - if (file.existsSync()) { - final Uint8List goldenData = await file.readAsBytes(); - - final Codec codec = await instantiateImageCodec(goldenData); - final FrameInfo frame = await codec.getNextFrame(); - expect(frame.image.height, equals(image.height)); - expect(frame.image.width, equals(image.width)); - - areEqual = await fuzzyCompareImages(frame.image, image); - } +void main() async { + final ImageComparer comparer = await ImageComparer.create(); - if (!areEqual) { - saveTestImage(image, 'found_$goldenImageName'); - } - return areEqual; -} - -void main() { testNoCrashes(); test('Simple .toImage', () async { @@ -190,11 +141,8 @@ void main() { }, 100, 100); expect(image.width, equals(100)); expect(image.height, equals(100)); - - final bool areEqual = - await fuzzyGoldenImageCompare(image, 'canvas_test_toImage.png'); - expect(areEqual, true); - }, skip: impellerEnabled); + await comparer.addGoldenImage(image, 'canvas_test_toImage.png'); + }); Gradient makeGradient() { return Gradient.linear( @@ -212,10 +160,8 @@ void main() { expect(image.width, equals(100)); expect(image.height, equals(100)); - final bool areEqual = - await fuzzyGoldenImageCompare(image, 'canvas_test_dithered_gradient.png'); - expect(areEqual, true); - }, skip: !Platform.isLinux || impellerEnabled); // https://github.com/flutter/flutter/issues/53784 + await comparer.addGoldenImage(image, 'canvas_test_dithered_gradient.png'); + }); test('Null values allowed for drawAtlas methods', () async { final Image image = await createImage(100, 100); @@ -302,12 +248,8 @@ void main() { }); }, width, height); - final bool areEqual = await fuzzyCompareImages(incrementalMatrixImage, combinedMatrixImage); + final bool areEqual = await comparer.fuzzyCompareImages(incrementalMatrixImage, combinedMatrixImage); - if (!areEqual) { - saveTestImage(incrementalMatrixImage, 'incremental_3D_transform_test_image.png'); - saveTestImage(combinedMatrixImage, 'combined_3D_transform_test_image.png'); - } expect(areEqual, true); }); @@ -348,10 +290,8 @@ void main() { expect(image.width, equals(200)); expect(image.height, equals(250)); - final bool areEqual = - await fuzzyGoldenImageCompare(image, 'dotted_path_effect_mixed_with_stroked_geometry.png'); - expect(areEqual, true); - }, skip: !Platform.isLinux || impellerEnabled); // https://github.com/flutter/flutter/issues/53784 + await comparer.addGoldenImage(image, 'dotted_path_effect_mixed_with_stroked_geometry.png'); + }); test('Gradients with matrices in Paragraphs render correctly', () async { final Image image = await toImage((Canvas canvas) { @@ -400,10 +340,8 @@ void main() { expect(image.width, equals(600)); expect(image.height, equals(400)); - final bool areEqual = - await fuzzyGoldenImageCompare(image, 'text_with_gradient_with_matrix.png'); - expect(areEqual, true); - }, skip: !Platform.isLinux || impellerEnabled); // https://github.com/flutter/flutter/issues/53784 + await comparer.addGoldenImage(image, 'text_with_gradient_with_matrix.png'); + }); test('toImageSync - too big', () async { PictureRecorder recorder = PictureRecorder(); @@ -602,8 +540,8 @@ void main() { final Image tofuImage = await drawText('>\b<'); // The tab's image should be identical to the space's image but not the tofu's image. - final bool tabToSpaceComparison = await fuzzyCompareImages(tabImage, spaceImage); - final bool tabToTofuComparison = await fuzzyCompareImages(tabImage, tofuImage); + final bool tabToSpaceComparison = await comparer.fuzzyCompareImages(tabImage, spaceImage); + final bool tabToTofuComparison = await comparer.fuzzyCompareImages(tabImage, tofuImage); expect(tabToSpaceComparison, isTrue); expect(tabToTofuComparison, isFalse); diff --git a/testing/dart/goldens.dart b/testing/dart/goldens.dart new file mode 100644 index 0000000000000..5a70ecfc95f78 --- /dev/null +++ b/testing/dart/goldens.dart @@ -0,0 +1,129 @@ +// 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. + +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:path/path.dart' as path; +import 'package:skia_gold_client/skia_gold_client.dart'; + +import 'impeller_enabled.dart'; + +const String _kSkiaGoldWorkDirectoryKey = 'kSkiaGoldWorkDirectory'; + +/// A helper for doing image comparison (golden) tests. +/// +/// Contains utilities for comparing two images in memory that are expected to +/// be identical, or for adding images to Skia gold for comparison. +class ImageComparer { + ImageComparer._({ + required SkiaGoldClient client, + }) : _client = client; + + // Avoid talking to Skia gold for the force-multithreading variants. + static bool get _useSkiaGold => + !Platform.executableArguments.contains('--force-multithreading'); + + /// Creates an image comparer and authorizes. + static Future create({ + bool verbose = false, + }) async { + const String workDirectoryPath = + String.fromEnvironment(_kSkiaGoldWorkDirectoryKey); + if (workDirectoryPath.isEmpty) { + throw UnsupportedError( + 'Using ImageComparer requries defining kSkiaGoldWorkDirectoryKey.'); + } + + final Directory workDirectory = Directory( + impellerEnabled ? '${workDirectoryPath}_iplr' : workDirectoryPath, + )..createSync(); + final Map dimensions = { + 'impeller_enabled': impellerEnabled.toString(), + }; + final SkiaGoldClient client = isSkiaGoldClientAvailable && _useSkiaGold + ? SkiaGoldClient(workDirectory, + dimensions: dimensions, verbose: verbose) + : _FakeSkiaGoldClient(workDirectory, dimensions, verbose: verbose); + + await client.auth(); + return ImageComparer._(client: client); + } + + final SkiaGoldClient _client; + + /// Adds an [Image] to Skia Gold for comparison. + /// + /// The [fileName] must be unique. + Future addGoldenImage(Image image, String fileName) async { + final ByteData data = + (await image.toByteData(format: ImageByteFormat.png))!; + + final File file = File(path.join(_client.workDirectory.path, fileName)) + ..writeAsBytesSync(data.buffer.asUint8List()); + await _client.addImg( + fileName, + file, + screenshotSize: image.width * image.height, + ).catchError((dynamic error) { + print('Skia gold comparison failed: $error'); + throw Exception('Failed comparison: $fileName'); + }); + } + + Future fuzzyCompareImages(Image golden, Image testImage) async { + if (golden.width != testImage.width || golden.height != testImage.height) { + return false; + } + int getPixel(ByteData data, int x, int y) => + data.getUint32((x + y * golden.width) * 4); + final ByteData goldenData = (await golden.toByteData())!; + final ByteData testImageData = (await testImage.toByteData())!; + for (int y = 0; y < golden.height; y++) { + for (int x = 0; x < golden.width; x++) { + if (getPixel(goldenData, x, y) != getPixel(testImageData, x, y)) { + return false; + } + } + } + return true; + } +} + +// TODO(dnfield): add local comparison against baseline, +// https://github.com/flutter/flutter/issues/136831 +class _FakeSkiaGoldClient implements SkiaGoldClient { + _FakeSkiaGoldClient( + this.workDirectory, + this.dimensions, { + this.verbose = false, + }); + + @override + final Directory workDirectory; + + @override + final Map dimensions; + + @override + final bool verbose; + + @override + Future auth() async {} + + @override + Future addImg( + String testName, + File goldenFile, { + double differentPixelsRate = 0.01, + int pixelColorDelta = 0, + required int screenshotSize, + }) async {} + + @override + dynamic noSuchMethod(Invocation invocation) { + throw UnimplementedError(invocation.memberName.toString().split('"')[1]); + } +} diff --git a/testing/dart/paragraph_test.dart b/testing/dart/paragraph_test.dart index 771cf98ac575f..4af61bb936345 100644 --- a/testing/dart/paragraph_test.dart +++ b/testing/dart/paragraph_test.dart @@ -219,6 +219,74 @@ void main() { expect(line.end, 10); }); + test('getLineMetricsAt', () { + const double fontSize = 10.0; + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle( + fontSize: fontSize, + textDirection: TextDirection.rtl, + height: 2.0, + )); + builder.addText('Test\npppp'); + final Paragraph paragraph = builder.build(); + paragraph.layout(const ParagraphConstraints(width: 100.0)); + final LineMetrics? line = paragraph.getLineMetricsAt(1); + expect(line?.hardBreak, isTrue); + expect(line?.ascent, 15.0); + expect(line?.descent, 5.0); + expect(line?.height, 20.0); + expect(line?.width, 4 * 10.0); + expect(line?.left, 100.0 - 40.0); + expect(line?.baseline, 20.0 + 15.0); + expect(line?.lineNumber, 1); + }); + + test('line number', () { + const double fontSize = 10.0; + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(fontSize: fontSize)); + builder.addText('Test\n\nTest'); + final Paragraph paragraph = builder.build(); + paragraph.layout(const ParagraphConstraints(width: 100.0)); + expect(paragraph.numberOfLines, 3); + expect(paragraph.getLineNumberAt(4), 0); // first LF + expect(paragraph.getLineNumberAt(5), 1); // second LF + expect(paragraph.getLineNumberAt(6), 2); // "T" in the second "Test" + }); + + test('empty paragraph', () { + const double fontSize = 10.0; + final Paragraph paragraph = ParagraphBuilder(ParagraphStyle( + fontSize: fontSize, + )).build(); + paragraph.layout(const ParagraphConstraints(width: double.infinity)); + + expect(paragraph.getLineMetricsAt(0), isNull); + expect(paragraph.numberOfLines, 0); + expect(paragraph.getLineNumberAt(0), isNull); + }); + + test('OOB indices as input', () { + const double fontSize = 10.0; + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle( + fontSize: fontSize, + maxLines: 1, + ellipsis: 'BBB', + ))..addText('A' * 100); + final Paragraph paragraph = builder.build(); + paragraph.layout(const ParagraphConstraints(width: 100)); + + expect(paragraph.numberOfLines, 1); + + expect(paragraph.getLineMetricsAt(-1), isNull); + expect(paragraph.getLineMetricsAt(0), isNotNull); + expect(paragraph.getLineMetricsAt(1), isNull); + + expect(paragraph.getLineNumberAt(-1), isNull); + expect(paragraph.getLineNumberAt(0), 0); + expect(paragraph.getLineNumberAt(6), 0); + // The last 3 characters on the first line are ellipsized with BBB. + expect(paragraph.getLineMetricsAt(7), isNull); + }); + test('painting a disposed paragraph does not crash', () { final Paragraph paragraph = ParagraphBuilder(ParagraphStyle()).build(); paragraph.dispose(); diff --git a/testing/dart/pubspec.yaml b/testing/dart/pubspec.yaml index 771026fff3627..e13765a1972be 100644 --- a/testing/dart/pubspec.yaml +++ b/testing/dart/pubspec.yaml @@ -19,6 +19,7 @@ environment: dependencies: litetest: any path: any + skia_gold_client: any sky_engine: any vector_math: any vm_service: any @@ -29,8 +30,14 @@ dependency_overrides: path: ../../../third_party/dart/pkg/async_helper collection: path: ../../../third_party/dart/third_party/pkg/collection + crypto: + path: ../../../third_party/dart/third_party/pkg/crypto + engine_repo_tools: + path: ../../tools/pkg/engine_repo_tools expect: path: ../../../third_party/dart/pkg/expect + file: + path: ../../../third_party/dart/third_party/pkg/file/packages/file fixnum: path: ../../../third_party/dart/third_party/pkg/fixnum litetest: @@ -39,12 +46,20 @@ dependency_overrides: path: ../../../third_party/dart/pkg/meta path: path: ../../../third_party/dart/third_party/pkg/path + platform: + path: ../../../third_party/pkg/platform + process: + path: ../../../third_party/pkg/process protobuf: path: ../../../third_party/dart/third_party/pkg/protobuf/protobuf smith: path: ../../../third_party/dart/pkg/smith + skia_gold_client: + path: ../skia_gold_client sky_engine: path: ../../sky/packages/sky_engine + typed_data: + path: ../../../third_party/dart/third_party/pkg/typed_data vector_math: path: ../../../third_party/pkg/vector_math vm_service: diff --git a/testing/resources/canvas_test_dithered_gradient.png b/testing/resources/canvas_test_dithered_gradient.png deleted file mode 100644 index d8062f2dc1f35..0000000000000 Binary files a/testing/resources/canvas_test_dithered_gradient.png and /dev/null differ diff --git a/testing/resources/canvas_test_gradient.png b/testing/resources/canvas_test_gradient.png deleted file mode 100644 index 89f3f63dc518a..0000000000000 Binary files a/testing/resources/canvas_test_gradient.png and /dev/null differ diff --git a/testing/resources/canvas_test_toImage.png b/testing/resources/canvas_test_toImage.png deleted file mode 100644 index 75d02c2fa967c..0000000000000 Binary files a/testing/resources/canvas_test_toImage.png and /dev/null differ diff --git a/testing/resources/dotted_path_effect_mixed_with_stroked_geometry.png b/testing/resources/dotted_path_effect_mixed_with_stroked_geometry.png deleted file mode 100644 index a8aef6b9936d7..0000000000000 Binary files a/testing/resources/dotted_path_effect_mixed_with_stroked_geometry.png and /dev/null differ diff --git a/testing/resources/text_with_gradient_with_matrix.png b/testing/resources/text_with_gradient_with_matrix.png deleted file mode 100644 index b940c1ee4e08b..0000000000000 Binary files a/testing/resources/text_with_gradient_with_matrix.png and /dev/null differ diff --git a/testing/run_tests.py b/testing/run_tests.py index 419460f43275d..8074d81dd5b9f 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -606,6 +606,11 @@ def threading_description(self): return 'multithreaded' return 'single-threaded' + def impeller_enabled(self): + if self.enable_impeller: + return 'impeller swiftshader' + return 'skia software' + def gather_dart_test(build_dir, dart_file, options): kernel_file_name = os.path.basename(dart_file) + '.dill' @@ -637,8 +642,8 @@ def gather_dart_test(build_dir, dart_file, options): tester_name = 'flutter_tester' logger.info( - "Running test '%s' using '%s' (%s)", kernel_file_name, tester_name, - options.threading_description() + "Running test '%s' using '%s' (%s, %s)", kernel_file_name, tester_name, + options.threading_description(), options.impeller_enabled() ) forbidden_output = [] if 'unopt' in build_dir or options.expect_failure else [ '[ERROR' diff --git a/testing/scenario_app/pubspec.yaml b/testing/scenario_app/pubspec.yaml index 4c579aa4d8230..fb13f5848f68b 100644 --- a/testing/scenario_app/pubspec.yaml +++ b/testing/scenario_app/pubspec.yaml @@ -29,6 +29,8 @@ dependency_overrides: path: ../../../third_party/dart/third_party/pkg/collection crypto: path: ../../../third_party/dart/third_party/pkg/crypto + engine_repo_tools: + path: ../../tools/pkg/engine_repo_tools file: path: ../../../third_party/dart/third_party/pkg/file/packages/file meta: diff --git a/testing/skia_gold_client/README.md b/testing/skia_gold_client/README.md index c9d6a8ce56498..3e7f6ec2d5fea 100644 --- a/testing/skia_gold_client/README.md +++ b/testing/skia_gold_client/README.md @@ -15,15 +15,29 @@ The web UI is available on https://flutter-engine-gold.skia.org/. dependencies: [{"dependency": "goldctl"}] ``` -2. Add dependency in `pubspec.yaml`: +2. In the builder `.json` file, ensure the drone has a dependency on `goldctl`: + +```yaml + "dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:" + } + ], +``` + +3. Add dependency in `pubspec.yaml`: ```yaml dependencies: + # needed for skia_gold_client to avoid a cache miss. + engine_repo_tools: + path: /tools/pkg/engine_repo_tools skia_gold_client: path: /testing/skia_gold_client ``` -3. Use the client: +4. Use the client: ```dart import 'package:skia_gold_client/skia_gold_client.dart'; diff --git a/testing/skia_gold_client/lib/skia_gold_client.dart b/testing/skia_gold_client/lib/skia_gold_client.dart index b258de044b554..471aec03ece3f 100644 --- a/testing/skia_gold_client/lib/skia_gold_client.dart +++ b/testing/skia_gold_client/lib/skia_gold_client.dart @@ -6,6 +6,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:crypto/crypto.dart'; +import 'package:engine_repo_tools/engine_repo_tools.dart'; import 'package:path/path.dart' as path; import 'package:process/process.dart'; @@ -36,7 +37,13 @@ class SkiaGoldClient { /// /// [dimensions] allows to add attributes about the environment /// used to generate the screenshots. - SkiaGoldClient(this.workDirectory, { this.dimensions }); + SkiaGoldClient(this.workDirectory, { this.dimensions, this.verbose = false}); + + /// Whether to print verbose output from goldctl. + /// + /// This flag is intended for use in debugging CI issues, and should not + /// ordinarily be set to true. + final bool verbose; /// Allows to add attributes about the environment used to generate the screenshots. final Map? dimensions; @@ -95,6 +102,7 @@ class SkiaGoldClient { final List authCommand = [ _goldctl, 'auth', + if (verbose) '--verbose', '--work-dir', _tempPath, '--luci', ]; @@ -111,6 +119,9 @@ class SkiaGoldClient { ..writeln('stdout: ${result.stdout}') ..writeln('stderr: ${result.stderr}'); throw Exception(buf.toString()); + } else if (verbose) { + print('stdout:\n${result.stdout}'); + print('stderr:\n${result.stderr}'); } } @@ -134,6 +145,7 @@ class SkiaGoldClient { final List imgtestInitCommand = [ _goldctl, 'imgtest', 'init', + if (verbose) '--verbose', '--instance', _instance, '--work-dir', _tempPath, '--commit', commitHash, @@ -163,7 +175,11 @@ class SkiaGoldClient { ..writeln('stdout: ${result.stdout}') ..writeln('stderr: ${result.stderr}'); throw Exception(buf.toString()); + } else if (verbose) { + print('stdout:\n${result.stdout}'); + print('stderr:\n${result.stderr}'); } + } /// Executes the `imgtest add` command in the `goldctl` tool. @@ -229,6 +245,7 @@ class SkiaGoldClient { final List imgtestCommand = [ _goldctl, 'imgtest', 'add', + if (verbose) '--verbose', '--work-dir', _tempPath, '--test-name', cleanTestName(testName), '--png-file', goldenFile.path, @@ -243,6 +260,9 @@ class SkiaGoldClient { // is meant to inform when an unexpected result occurs. print('goldctl imgtest add stdout: ${result.stdout}'); print('goldctl imgtest add stderr: ${result.stderr}'); + } else if (verbose) { + print('stdout:\n${result.stdout}'); + print('stderr:\n${result.stderr}'); } } @@ -261,6 +281,7 @@ class SkiaGoldClient { final List tryjobInitCommand = [ _goldctl, 'imgtest', 'init', + if (verbose) '--verbose', '--instance', _instance, '--work-dir', _tempPath, '--commit', commitHash, @@ -293,6 +314,9 @@ class SkiaGoldClient { ..writeln('stdout: ${result.stdout}') ..writeln('stderr: ${result.stderr}'); throw Exception(buf.toString()); + } else if (verbose) { + print('stdout:\n${result.stdout}'); + print('stderr:\n${result.stderr}'); } } @@ -317,6 +341,7 @@ class SkiaGoldClient { final List tryjobCommand = [ _goldctl, 'imgtest', 'add', + if (verbose) '--verbose', '--work-dir', _tempPath, '--test-name', cleanTestName(testName), '--png-file', goldenFile.path, @@ -338,6 +363,9 @@ class SkiaGoldClient { ..writeln('stderr: ${result.stderr}') ..writeln(); throw Exception(buf.toString()); + } else if (verbose) { + print('stdout:\n${result.stdout}'); + print('stderr:\n${result.stderr}'); } } @@ -421,13 +449,13 @@ class SkiaGoldClient { /// Returns the current commit hash of the engine repository. Future _getCurrentCommit() async { - final File currentScript = File.fromUri(Platform.script); + final String engineCheckout = Engine.findWithin().flutterDir.path; final ProcessResult revParse = await process.run( ['git', 'rev-parse', 'HEAD'], - workingDirectory: currentScript.parent.absolute.path, + workingDirectory: engineCheckout, ); if (revParse.exitCode != 0) { - throw Exception('Current commit of the engine can not be found from path ${currentScript.path}.'); + throw Exception('Current commit of the engine can not be found from path $engineCheckout.'); } return (revParse.stdout as String).trim(); } diff --git a/testing/skia_gold_client/pubspec.yaml b/testing/skia_gold_client/pubspec.yaml index 8c7bc0a6a7ff9..693424d5350d6 100644 --- a/testing/skia_gold_client/pubspec.yaml +++ b/testing/skia_gold_client/pubspec.yaml @@ -17,6 +17,7 @@ environment: dependencies: crypto: any path: any + engine_repo_tools: any process: any dependency_overrides: @@ -24,6 +25,8 @@ dependency_overrides: path: ../../../third_party/dart/third_party/pkg/collection crypto: path: ../../../third_party/dart/third_party/pkg/crypto + engine_repo_tools: + path: ../../tools/pkg/engine_repo_tools file: path: ../../../third_party/dart/third_party/pkg/file/packages/file meta: diff --git a/third_party/txt/src/skia/paragraph_skia.cc b/third_party/txt/src/skia/paragraph_skia.cc index 67d67b1a04d2b..cbb7b85d01f41 100644 --- a/third_party/txt/src/skia/paragraph_skia.cc +++ b/third_party/txt/src/skia/paragraph_skia.cc @@ -301,6 +301,11 @@ std::vector& ParagraphSkia::GetLineMetrics() { return line_metrics_.value(); } +bool ParagraphSkia::GetLineMetricsAt(int lineNumber, + skt::LineMetrics* lineMetrics) const { + return paragraph_->getLineMetricsAt(lineNumber, lineMetrics); +}; + double ParagraphSkia::GetMinIntrinsicWidth() { return SkScalarToDouble(paragraph_->getMinIntrinsicWidth()); } @@ -378,6 +383,14 @@ Paragraph::Range ParagraphSkia::GetWordBoundary(size_t offset) { return Paragraph::Range(range.start, range.end); } +size_t ParagraphSkia::GetNumberOfLines() const { + return paragraph_->lineNumber(); +} + +int ParagraphSkia::GetLineNumberAt(size_t codeUnitIndex) const { + return paragraph_->getLineNumberAtUTF16Offset(codeUnitIndex); +} + TextStyle ParagraphSkia::SkiaToTxt(const skt::TextStyle& skia) { TextStyle txt; diff --git a/third_party/txt/src/skia/paragraph_skia.h b/third_party/txt/src/skia/paragraph_skia.h index 5779445e09a71..bc23b78538b45 100644 --- a/third_party/txt/src/skia/paragraph_skia.h +++ b/third_party/txt/src/skia/paragraph_skia.h @@ -50,6 +50,14 @@ class ParagraphSkia : public Paragraph { std::vector& GetLineMetrics() override; + bool GetLineMetricsAt( + int lineNumber, + skia::textlayout::LineMetrics* lineMetrics) const override; + + size_t GetNumberOfLines() const override; + + int GetLineNumberAt(size_t utf16Offset) const override; + bool DidExceedMaxLines() override; void Layout(double width) override; diff --git a/third_party/txt/src/txt/paragraph.h b/third_party/txt/src/txt/paragraph.h index 2d736fb3935c5..b8ee55d4366f5 100644 --- a/third_party/txt/src/txt/paragraph.h +++ b/third_party/txt/src/txt/paragraph.h @@ -22,6 +22,8 @@ #include "line_metrics.h" #include "paragraph_style.h" #include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/modules/skparagraph/include/Metrics.h" +#include "third_party/skia/modules/skparagraph/include/Paragraph.h" class SkCanvas; @@ -178,6 +180,22 @@ class Paragraph { virtual Range GetWordBoundary(size_t offset) = 0; virtual std::vector& GetLineMetrics() = 0; + + virtual bool GetLineMetricsAt( + int lineNumber, + skia::textlayout::LineMetrics* lineMetrics) const = 0; + + // Returns the total number of visible lines in the paragraph. + virtual size_t GetNumberOfLines() const = 0; + + // Returns the zero-indexed line number that contains the given code unit + // offset. Returns -1 if the given offset is out of bounds, or points to a + // codepoint that is logically after the last visible codepoint. + // + // If the offset points to a hard line break, this method returns the line + // number of the line this hard line break breaks, intead of the new line it + // creates. + virtual int GetLineNumberAt(size_t utf16Offset) const = 0; }; } // namespace txt diff --git a/web_sdk/web_test_utils/pubspec.yaml b/web_sdk/web_test_utils/pubspec.yaml index ca7efd336eec7..fbdd1be557741 100644 --- a/web_sdk/web_test_utils/pubspec.yaml +++ b/web_sdk/web_test_utils/pubspec.yaml @@ -16,3 +16,7 @@ dependencies: path: ../../testing/skia_gold_client typed_data: 1.3.0 yaml: 3.0.0 + +dependency_overrides: + engine_repo_tools: + path: ../../tools/pkg/engine_repo_tools